View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.convert;
20  
21  import java.text.DecimalFormat;
22  import java.text.DecimalFormatSymbols;
23  import java.text.NumberFormat;
24  import java.text.ParsePosition;
25  
26  import java.util.Currency;
27  import java.util.HashMap;
28  import java.util.Locale;
29  import java.util.Map;
30  
31  import java.util.regex.Matcher;
32  
33  import javax.el.ValueExpression;
34  
35  import javax.faces.application.FacesMessage;
36  import javax.faces.component.UIComponent;
37  import javax.faces.context.FacesContext;
38  import javax.faces.convert.ConverterException;
39  
40  import javax.faces.el.ValueBinding;
41  
42  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFConverter;
43  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
44  import org.apache.myfaces.trinidad.bean.FacesBean;
45  import org.apache.myfaces.trinidad.bean.PropertyKey;
46  import org.apache.myfaces.trinidad.context.RequestContext;
47  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
48  import org.apache.myfaces.trinidad.util.ComponentUtils;
49  import org.apache.myfaces.trinidad.util.MessageFactory;
50  
51  /**
52   * This is an extension of the standard JSF {@link javax.faces.convert.NumberConverter}
53   * The converter provides all the standard functionality
54   * of the default NumberConverter and is strict while converting to object.
55   *
56   * JSF {@link javax.faces.convert.NumberConverter} is lenient and will convert
57   * values like 22.22.2 or 22ABC to valid number 22.22 and 22 respectively,
58   * here it would result in a conversion failure and would throw
59   * ConverterException.
60   *
61   * If <code>number grouping separator, decimal separator</code>
62   * is configured in <code>trinidad-config.xml</code> file,
63   * it will be used during call to <code>getAsObject()</code> and
64   * <code>getAsString()</code> for parsing and formatting. If it has not been set,
65   * <code>number grouping separator, decimal separator</code> is
66   * defaulted based on the locale.</p>
67   *
68   * <p>If <code>currencyCode</code> is set on the converter then it will be used.
69   * Else uses the <code>currencyCode</code> set on <code>trinidad-config.xml</code>
70   * file. If it is not set, then it is defaulted based on the locale. The
71   * value registered in trinidad-config.xml is obtained using
72   * api from {@link org.apache.myfaces.trinidad.context.RequestContext}.</p>
73   *
74   * <p>Since Apache Trinidad is compatible only with JDK 1.4 and higher versions,
75   * the <code>currencyCode</code> gets preference over <code>currencySymbol</code>
76   * See RI's {@link javax.faces.convert.NumberConverter} for the way in which
77   * <code>currencyCode</code> and <code>currencySymbol</code> gets preference for
78   * different version of JDK.
79   *
80   *<p>The <code>detail</code> part of the {@link FacesMessage} can be customized.
81   * For each message id there is a corresponding
82   * setter method, which provides for message customization. The customized
83   * messages can contain placeholders as specified in the documentation
84   * for its corresponding message id.</p>
85   *
86   * <p>Example: to customize the message for invalid input values, which will result
87   * in conversion error containing {@link #CONVERT_NUMBER_MESSAGE_ID}, the following,
88   * can be done.</p>
89   * <code>
90   * String convertNumberMessageDetail = "{0}" in "{1}" is not valid age.</code>
91   *
92   * //  Note that, the string can contain placeholders and it will be replaced
93   * //  appropriately as specified in the documentation for the corresponding
94   * //  message id.
95   *
96   * <code>setMessageDetailConvertNumber(convertNumberMessageDetail);</code>
97   *
98   * This way user can override detail part of the {@link FacesMessage} for
99   * different conversion errors that occur for wrong values, that arise
100  * during conversion.
101  *
102  * 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
103  * is given below:</p>
104  * <ol><code>
105  * <li>{@link #CONVERT_PATTERN_MESSAGE_ID} - {@link #setMessageDetailConvertPattern(String)}</li>
106  * <li>{@link #CONVERT_NUMBER_MESSAGE_ID} -  {@link #setMessageDetailConvertNumber(String)}</li>
107  * <li>{@link #CONVERT_CURRENCY_MESSAGE_ID} - {@link #setMessageDetailConvertCurrency(String)}</li>
108  * <li>{@link #CONVERT_PERCENT_MESSAGE_ID} - {@link #setMessageDetailConvertPercent(String)}</li>
109  * </code></ol>The custom messages can contain placeholders, which will be replaced with
110  * values as specified in its corresponding message id.
111  *
112  * <p>
113  * This NumberConverter is automatically registered under the standard
114  * converter ID, and therefore will be used when the
115  * <code>&lt;f:convertNumber&gt;</code> tag is used.
116  *
117  * @see org.apache.myfaces.trinidad.context.RequestContext
118  *
119  * <p>
120  */
121 @JSFConverter(configExcluded=true)
122 public class NumberConverter extends javax.faces.convert.NumberConverter
123 {
124 
125   /**
126    * <p>The standard converter id for this converter.</p>
127    */
128     public static final String CONVERTER_ID = "org.apache.myfaces.trinidad.Number";
129 
130   /**
131    * <p>The message identifier of the {@link FacesMessage}
132    * to be created if the input value does not match the specified pattern.
133    * The message format string for this message may optionally
134    * include <code>{0}</code>, <code>{1}</code> and <code>{4}</code>
135    * placeholders, which will be replaced with the input value,
136    * label associated with the component and the pattern respectively.</p>
137    */
138    public static final String CONVERT_PATTERN_MESSAGE_ID =
139     "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_PATTERN";
140 
141   /**
142    * <p>The message identifier of the {@link FacesMessage}
143    * to be created if the input value is not a valid number.  The message format
144    * string for this message may optionally include <code>{0}</code> and
145    * <code>{1}</code> placeholders, which will be replaced with
146    * the input value and label associated with the component
147    * respectively.</p>
148    */
149    public static final String CONVERT_NUMBER_MESSAGE_ID =
150     "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_NUMBER";
151 
152   /**
153    * <p>The message identifier of the {@link FacesMessage}
154    * to be created if the input value is not a valid number when <code>type</code>
155    * is set to <code>'currency'</code>. The message format
156    * string for this message may optionally include <code>{0}</code> and
157    * <code>{1}</code> placeholders, which will be replaced with
158    * the input value and label associated with the component
159    * respectively.</p>
160    */
161    public static final String CONVERT_CURRENCY_MESSAGE_ID =
162     "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_CURRENCY";
163 
164    /**
165    * <p>The message identifier of the {@link FacesMessage}
166    * to be created if the input value is not a valid number when <code>type</code>
167    * is set to <code>'percent'</code>. The message format
168    * string for this message may optionally include <code>{0}</code> and
169    * <code>{1}</code> placeholders, which will be replaced with
170    * the input value and label associated with the component
171    * respectively.</p>
172    */
173    public static final String CONVERT_PERCENT_MESSAGE_ID =
174     "org.apache.myfaces.trinidad.convert.NumberConverter.CONVERT_PERCENT";
175 
176 
177   //Converter interface implementation
178   /**
179    * Performs strict conversion of string to number.
180    * Values having more than one decimal seprator like <code>22.22.22</code>
181    * and values of the form <code>22ABC, 22%ABC</code> will result in
182    * {@link javax.faces.convert.ConverterException}.
183    */
184   @Override
185   public Object getAsObject(
186     FacesContext context,
187     UIComponent component,
188     String value)
189   {
190     if (null == context || null == component)
191     {
192       throw new NullPointerException(_LOG.getMessage(
193         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
194     }
195 
196     if (null == value)
197       return null;
198 
199     if (isDisabled())
200       return value;
201 
202     value = value.trim();
203     if (value.length() < 1)
204       return null;
205 
206     String pattern = getPattern();
207     String type = getType();
208 
209     if (null == pattern && null == type)
210     {
211       throw new IllegalArgumentException(_LOG.getMessage(
212         "EITHER_PATTERN_OR_TYPE_MUST_SPECIFIED"));
213     }
214 
215     RequestContext reqCtx = RequestContext.getCurrentInstance();
216     Locale locale = _getLocale(reqCtx, context);
217 
218     NumberFormat fmt = _getNumberFormat(pattern, type, locale, reqCtx);
219     
220     DecimalFormat df = (DecimalFormat)fmt;
221     df.setParseBigDecimal(true); // TODO What does this do?
222     DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
223     
224     // We change the grouping_separator b/c TRINIDAD-849
225     // source is this JDK bug: 4510618.
226     boolean changed = false;
227     if (dfs.getGroupingSeparator() == '\u00a0')
228     {
229       // In some locales, such as fr_FR, the grouping separator is '\u00a0', a 
230       // non-breaking space. However, users will normally enter a regular space 
231       // character into an input field, so in order for the input to be parsed 
232       // correctly, we set the grouping separator to a regular space character. 
233       dfs.setGroupingSeparator(' ');
234       df.setDecimalFormatSymbols(dfs);
235       
236       // In the (rare) case that the user actually enters a non-breaking space, 
237       // we replace it with a regular space. This should be fine, since the 
238       // percent format for fr_FR is " %" (regular space followed by percent).
239       value = value.replace('\u00a0', ' ');
240       
241       changed = true;
242     }
243     
244     ParsePosition pp = new ParsePosition(0);  
245     Number num = (Number)fmt.parseObject(value, pp);   
246     
247     // The following determines whether the percent/currency symbol was left off.
248     int typeIdx = _getType(pattern, type);
249     if (num == null && (typeIdx == _CURRENCY_TYPE || typeIdx == _PERCENT_TYPE))
250     {
251       // For parsing 'value' as a Number when the percent/currency symbol is left off.
252       NumberFormat nfmt = NumberFormat.getNumberInstance(locale);
253       DecimalFormat ndf = (DecimalFormat)nfmt;
254       ndf.setParseBigDecimal(true); // TODO What does this do?
255       DecimalFormatSymbols ndfs = null;
256       
257       if (changed)
258       {
259         ndfs = ndf.getDecimalFormatSymbols();
260         ndfs.setGroupingSeparator(' ');
261         ndf.setDecimalFormatSymbols(ndfs);
262       }
263       
264       // Assume the percent/currency symbol was left off, in which case we should 
265       // be able to parse 'value' as a Number.
266       // An error occured, so the index of pp should still be 0.
267       num = (Number)nfmt.parseObject(value, pp);      
268       if (typeIdx == _PERCENT_TYPE && num != null)
269         num = num.doubleValue() / 100.0;
270     }
271     
272     // Change it back, since we could have been handed a cached reference. This 
273     // may not be thread-safe, but it probably doesn't have to be.
274     if (changed)
275     {
276       dfs.setGroupingSeparator('\u00a0');
277       df.setDecimalFormatSymbols(dfs);
278     }
279 
280     if (pp.getIndex() != value.length())
281     {
282       // According to the comments in 
283       // trinidad-api\src\main\xrts\org\apache\myfaces\trinidad\resource\MessageBundle.xrts,
284       // the substitution parameters are supposed to be:
285       // {0} the label that identifies the component
286       // {1} value entered by the user
287       Object label = ConverterUtils.getComponentLabel(component);
288       Object[] params = null;
289       
290       if (typeIdx == _PATTERN_TYPE)
291       {
292         // We call this since the pattern may contain the generic currency sign, which we don't 
293         // want to display to the user.
294         pattern = getLocalizedPattern(context, pattern, dfs);
295         
296         params = new Object[] {label, value, pattern};
297       }
298       else if (typeIdx == _NUMBER_TYPE)
299       {
300         params = new Object[] {label, value};
301       }
302       else if (typeIdx == _CURRENCY_TYPE)
303       {
304         params = new Object[] {label, value, fmt.format(_EXAMPLE_CURRENCY)};
305       }
306       else if (typeIdx == _PERCENT_TYPE)
307       {
308         params = new Object[] {label, value, fmt.format(_EXAMPLE_PERCENT)};
309       }
310         
311       throw new ConverterException(
312         getConvertMessage(context, component, value, params));
313     }
314 
315     // if we set setParseIntegerOnly(isIntegerOnly()) - This may result in
316     // the formatter stopping to parse after the first decimal point.
317     // that is number of value 222.22 which is legitimate, hence our test would
318     // fail. hence we did not do the following
319     // fmt.setParseIntegerOnly(isIntegerOnly());
320     // We allow the value to be totally parsed and if the user has set
321     // to integer only. We will return the long value from the number object
322     // we have in hand.
323     if (isIntegerOnly())
324       return Long.valueOf(num.longValue());
325 
326     return num;
327   }
328 
329   /**
330    *
331    * @param context {@link FacesContext} for the request being processed
332    * @param component {@link UIComponent} with which this model object
333    *        value is associated.
334    * @param value Model object value to be converted (may be <code>null</code>)
335    *
336    * @return a zero-length String if value is <code>null</code>,
337    *         if the passed value is a String, it's returned unchanged,
338    *         otherwise String representation for the number object based on the
339    *         attributes set.
340    * @exception ConverterException if conversion cannot be successfully
341    *            performed
342    * @exception NullPointerException if <code>context</code> or
343    *            <code>component</code> is <code>null</code>
344    * @exception IllegalArgumentException if the <code>value</code> is not of
345    *            type other than {@link java.lang.Number}, {@link java.lang.String}.
346    *            <code>value</code> can be null.
347    */
348   @Override
349   public String getAsString(
350     FacesContext context,
351     UIComponent component,
352     Object value)
353   {
354     if ( null == context || null == component )
355     {
356       throw new NullPointerException(_LOG.getMessage(
357         "NULL_FACESCONTEXT_OR_UICOMPONENT"));
358     }
359 
360     if(value == null)
361      return "";
362 
363     if(value instanceof String)
364       return (String)value;
365 
366     if (isDisabled())
367       return value.toString();
368 
369     if (!(value instanceof Number))
370       throw new IllegalArgumentException(_LOG.getMessage(
371         "VALUE_NOT_JAVA_LANG_NUMBER_TYPE"));
372 
373     String pattern = getPattern();
374     String type    = getType();
375 
376     if (null == pattern && null == type)
377     {
378       throw new IllegalArgumentException(_LOG.getMessage(
379         "EITHER_PATTERN_OR_TYPE_MUST_SPECIFIED"));
380     }
381 
382 
383     RequestContext reqCtx = RequestContext.getCurrentInstance();
384     Locale locale  = _getLocale(reqCtx, context);
385 
386     NumberFormat formatter = _getNumberFormat(pattern, type, locale, reqCtx);
387 
388     _setFormatProperties(formatter);
389 
390     if("currency".equals(type))
391     {
392       _setCurrencyFormattingProperties(reqCtx, formatter);
393     }
394 
395     return formatter.format(value);
396   }
397 
398   @Override
399   public void restoreState(
400     FacesContext context,
401     Object state)
402   {
403     _facesBean.restoreState(context, state);
404   }
405 
406   @Override
407   public Object saveState(FacesContext context)
408   {
409     return _facesBean.saveState(context);
410   }
411 
412   /**
413    * <p>Set the {@link ValueExpression} used to calculate the value for the
414    * specified attribute if any.</p>
415    *
416    * @param name Name of the attribute for which to set a {@link ValueExpression}
417    * @param expression The {@link ValueExpression} to set, or <code>null</code>
418    *  to remove any currently set {@link ValueExpression}
419    *
420    * @exception NullPointerException if <code>name</code>
421    *  is <code>null</code>
422    * @exception IllegalArgumentException if <code>name</code> is not a valid
423    *            attribute of this converter
424    */
425   public void setValueExpression(String name, ValueExpression expression)
426   {
427     ConverterUtils.setValueExpression(_facesBean, name, expression) ;
428   }
429 
430 
431   /**
432    * <p>Return the {@link ValueExpression} used to calculate the value for the
433    * specified attribute name, if any.</p>
434    *
435    * @param name Name of the attribute or property for which to retrieve a
436    *  {@link ValueExpression}
437    *
438    * @exception NullPointerException if <code>name</code>
439    *  is <code>null</code>
440    * @exception IllegalArgumentException if <code>name</code> is not a valid
441    * attribute of this converter
442    */
443   public ValueExpression getValueExpression(String name)
444   {
445     return ConverterUtils.getValueExpression(_facesBean, name);
446   }
447 
448   /**
449    * <p>Set the {@link ValueBinding} used to calculate the value for the
450    * specified attribute if any.</p>
451    *
452    * @param name Name of the attribute for which to set a {@link ValueBinding}
453    * @param binding The {@link ValueBinding} to set, or <code>null</code>
454    *  to remove any currently set {@link ValueBinding}
455    *
456    * @exception NullPointerException if <code>name</code>
457    *  is <code>null</code>
458    * @exception IllegalArgumentException if <code>name</code> is not a valid
459    *            attribute of this converter
460    * @deprecated
461    */
462   public void setValueBinding(String name, ValueBinding binding)
463   {
464     ConverterUtils.setValueBinding(_facesBean, name, binding) ;
465   }
466 
467   /**
468    * <p>Return the {@link ValueBinding} used to calculate the value for the
469    * specified attribute name, if any.</p>
470    *
471    * @param name Name of the attribute or property for which to retrieve a
472    *  {@link ValueBinding}
473    *
474    * @exception NullPointerException if <code>name</code>
475    *  is <code>null</code>
476    * @exception IllegalArgumentException if <code>name</code> is not a valid
477    * attribute of this converter
478    * @deprecated
479    */
480   public ValueBinding getValueBinding(String name)
481   {
482     return ConverterUtils.getValueBinding(_facesBean, name);
483   }
484 
485   /**
486    * Custom error message to be used, for creating detail part of the {@link FacesMessage},
487    * message, when <code>value</code> cannot be converted to a number,
488    * based on the <code>pattern</code> set.
489    * Overrides detail message identified by message id {@link #CONVERT_PATTERN_MESSAGE_ID}
490    * @param convertPatternMessageDetail Custom error message.
491    */
492   public void setMessageDetailConvertPattern(String convertPatternMessageDetail)
493   {
494     _facesBean.setProperty(_CONVERT_PATTERN_MESSAGE_DETAIL_KEY, convertPatternMessageDetail);
495   }
496 
497   /**
498    * Custom detail error message that was set for creation of {@link FacesMessage}
499    * when conversion fails for values that does not match the pattern set.
500    * @return Custom error message.
501    * @see #setMessageDetailConvertPattern(String)
502    *
503    */
504   @JSFProperty
505   public String getMessageDetailConvertPattern()
506   {
507     Object msg = _facesBean.getProperty(_CONVERT_PATTERN_MESSAGE_DETAIL_KEY);
508     return ComponentUtils.resolveString(msg);
509   }
510 
511   /**
512    * <p>Custom error message to be used, for creating detail part of the {@link FacesMessage},
513    * when <code>value</code> cannot be converted to a number,
514    * when <code>type</code> is set to <code>'number'</code> and
515    * <code>pattern</code> is null or not set.</p>
516    * Overrides detail message identified by message id {@link #CONVERT_NUMBER_MESSAGE_ID}
517    * @param convertNumberMessageDetail custom error message.
518    */
519   public void setMessageDetailConvertNumber(String convertNumberMessageDetail)
520   {
521     _facesBean.setProperty(_CONVERT_NUMBER_MESSAGE_DETAIL_KEY, convertNumberMessageDetail);
522   }
523 
524   /**
525    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
526    * when conversion fails for values, when <code>type</code> is set to <code>'number'</code> and
527    * <code>pattern</code> is null or not set.</p>
528    * @return Custom error message.
529    * @see #setMessageDetailConvertNumber(String)
530    */
531   @JSFProperty
532   public String getMessageDetailConvertNumber()
533   {
534     Object msg = _facesBean.getProperty(_CONVERT_NUMBER_MESSAGE_DETAIL_KEY);
535     return ComponentUtils.resolveString(msg);
536   }
537 
538   /**
539    * Custom error message to be used, for creating detail part of the
540    * {@link FacesMessage},  when <code>value</code>
541    * cannot be converted to a number, when <code>type</code> is set to
542    * <code>'currency'</code> and <code>pattern</code> is null or not set.</p>
543    * Overrides detail message identified by message id {@link #CONVERT_CURRENCY_MESSAGE_ID}.
544    * @param convertCurrencyMessageDetail custom error message.
545    *
546    */
547   public void setMessageDetailConvertCurrency(String convertCurrencyMessageDetail)
548   {
549     _facesBean.setProperty(_CONVERT_CURRENCY_MESSAGE_DETAIL_KEY,convertCurrencyMessageDetail);
550   }
551 
552   /**
553    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
554    * when conversion fails for values, when <code>type</code> is set to
555    * <code>'currency'</code> and <code>pattern</code> is null or not set.</p>
556    * @return Custom error message.
557    * @see #setMessageDetailConvertCurrency(String)
558    */
559   @JSFProperty
560   public String getMessageDetailConvertCurrency()
561   {
562     Object msg = _facesBean.getProperty(_CONVERT_CURRENCY_MESSAGE_DETAIL_KEY);
563     return ComponentUtils.resolveString(msg);
564   }
565 
566   /**
567    * Custom error message to be used, for creating detail part of the
568    * {@link FacesMessage}, when <code>value</code> cannot be converted to a
569    * number, when <code>type</code> is set to <code>'percent'</code> and
570    * <code>pattern</code> is null or not set.</p>
571    * Overrides detail message identified by message id {@link #CONVERT_PERCENT_MESSAGE_ID}
572    * @param convertPercentMessageDetail custom error message.
573    */
574   public void setMessageDetailConvertPercent(String convertPercentMessageDetail)
575   {
576     _facesBean.setProperty(_CONVERT_PERCENT_MESSAGE_DETAIL_KEY, convertPercentMessageDetail);
577   }
578 
579   /**
580    * <p>Return custom detail error message that was set for creating {@link FacesMessage},
581    * when conversion fails for values, when <code>value</code> cannot be converted to a
582    * number, when <code>type</code> is set to <code>'percent'</code>
583    * and <code>pattern</code> is null or not set.</p>
584    * @return Custom error message.
585    * @see #setMessageDetailConvertPercent(String)
586    */
587   @JSFProperty
588   public String getMessageDetailConvertPercent()
589   {
590     Object msg = _facesBean.getProperty(_CONVERT_PERCENT_MESSAGE_DETAIL_KEY);
591     return ComponentUtils.resolveString(msg);
592   }
593   
594   /**
595    * <p>Custom hintPattern message.</p>
596    * Overrides default hint message
597    * @param hintPattern Custom hint message.
598    */
599   public void setHintPattern(String hintPattern)
600   {
601     _facesBean.setProperty(_HINT_PATTERN_KEY, hintPattern);
602   }
603 
604   /**
605    * <p>Return custom hintPattern message.</p>
606    * @return Custom hint message.
607    * @see  #setHintPattern(String)
608    */
609   public String getHintPattern()
610   {
611     Object obj = _facesBean.getProperty(_HINT_PATTERN_KEY);
612     return ComponentUtils.resolveString(obj);
613   }
614 
615   
616 
617   @Override
618   public void setCurrencyCode(String currencyCode)
619   {
620     _facesBean.setProperty(_CURRENCY_CODE_KEY, currencyCode);
621   }
622 
623   @JSFProperty
624   @Override
625   public String getCurrencyCode()
626   {
627     Object currCode = _facesBean.getProperty(_CURRENCY_CODE_KEY);
628     return ComponentUtils.resolveString(currCode);
629   }
630 
631   @Override
632   public void setCurrencySymbol(String currencySymbol)
633   {
634     _facesBean.setProperty(_CURRENCY_SYMBOL_KEY, currencySymbol);
635   }
636 
637   @JSFProperty
638   @Override
639   public String getCurrencySymbol()
640   {
641     Object currSymbol = _facesBean.getProperty(_CURRENCY_SYMBOL_KEY);
642     return ComponentUtils.resolveString(currSymbol);
643   }
644 
645   @Override
646   public void setGroupingUsed(boolean groupingUsed)
647   {
648     Boolean grpUsed = _getBooleanValue(groupingUsed);
649     _facesBean.setProperty(_GROUPING_USED_KEY, grpUsed);
650   }
651 
652   @JSFProperty(defaultValue="true")
653   @Override
654   public  boolean isGroupingUsed()
655   {
656     Object grpUSed = _facesBean.getProperty(_GROUPING_USED_KEY);
657     return ComponentUtils.resolveBoolean(grpUSed, true);
658   }
659 
660   @Override
661   public void setIntegerOnly(boolean integerOnly)
662   {
663     _facesBean.setProperty(_INTEGER_ONLY_KEY, _getBooleanValue(integerOnly));
664   }
665 
666   @JSFProperty(defaultValue="false")
667   @Override
668   public boolean isIntegerOnly()
669   {
670     Object isInt = _facesBean.getProperty(_INTEGER_ONLY_KEY);
671     return ComponentUtils.resolveBoolean(isInt, false);
672   }
673 
674   /**
675    * <p>Set the <code>Locale</code> to be used when parsing numbers.
676    * If set to <code>null</code>, the <code>Locale</code> stored in the
677    * {@link javax.faces.component.UIViewRoot} for the current request
678    * will be utilized.</p>
679    *
680    * @param locale The new <code>Locale</code> (or <code>null</code>)
681    */
682   @Override
683   public void setLocale(Locale locale)
684   {
685     _facesBean.setProperty(_LOCALE_KEY, locale);
686   }
687   /**
688    * <p>Return the <code>Locale</code> that was set, returns null if it was not set,
689    * while faces RI returns the <code>Locale</code> set on the view root if the locale
690    * is null.
691    * If this value is <code>null</code>, the <code>Locale</code> stored
692    * in the {@link javax.faces.component.UIViewRoot} for the current request
693    * will be utilized during parsing.</p>
694    */
695   @JSFProperty
696   @Override
697   public Locale getLocale()
698   {
699     Object locale = _facesBean.getProperty(_LOCALE_KEY);
700     return ComponentUtils.resolveLocale(locale);
701   }
702 
703   // All these overrides are mainly to identify whether these were set or nor in
704   // the first place
705 
706   @Override
707   public void setMaxFractionDigits(int maxFractionDigits)
708   {
709     _facesBean.setProperty(_MAX_FRACTION_DIGITS_KEY, _getIntValue(maxFractionDigits));
710   }
711 
712   @JSFProperty
713   @Override
714   public int getMaxFractionDigits()
715   {
716     Object value = _facesBean.getProperty(_MAX_FRACTION_DIGITS_KEY);
717     return ComponentUtils.resolveInteger(value);
718   }
719 
720   @Override
721   public void setMaxIntegerDigits(int maxIntegerDigits)
722   {
723     _facesBean.setProperty(_MAX_INTEGER_DIGITS_KEY, _getIntValue(maxIntegerDigits));
724   }
725 
726   @JSFProperty
727   @Override
728   public int getMaxIntegerDigits()
729   {
730     Object value = _facesBean.getProperty(_MAX_INTEGER_DIGITS_KEY);
731     return ComponentUtils.resolveInteger(value);
732   }
733 
734   @Override
735   public void setMinFractionDigits(int minFractionDigits)
736   {
737     _facesBean.setProperty(_MIN_FRACTION_DIGITS_KEY, _getIntValue(minFractionDigits));
738   }
739 
740   @JSFProperty
741   @Override
742   public int getMinFractionDigits()
743   {
744     Object value = _facesBean.getProperty(_MIN_FRACTION_DIGITS_KEY);
745     return ComponentUtils.resolveInteger(value);
746   }
747 
748   @Override
749   public void setMinIntegerDigits(int minIntegerDigits)
750   {
751     _facesBean.setProperty(_MIN_INTEGER_DIGITS_KEY, _getIntValue(minIntegerDigits));
752   }
753 
754   @JSFProperty
755   @Override
756   public int getMinIntegerDigits()
757   {
758     Object value = _facesBean.getProperty(_MIN_INTEGER_DIGITS_KEY);
759     return ComponentUtils.resolveInteger(value);
760   }
761 
762   @Override
763   public void setPattern(String pattern)
764   {
765     _facesBean.setProperty(_PATTERN_KEY, pattern);
766   }
767 
768   @JSFProperty
769   @Override
770   public String getPattern()
771   {
772     Object pattern = _facesBean.getProperty(_PATTERN_KEY);
773     return ComponentUtils.resolveString(pattern, true);
774   }
775 
776   /**
777    * If <code>pattern</code> contains the generic currency sign, this method will replace it 
778    * with the localized currency symbol (if one exists). 
779    * @param context the FacesContext
780    * @param pattern the pattern to be localized
781    * @param dfs the DecimalFormatSymbols; if null, will be constructed from the <code>context</code>
782    * @return
783    */
784   public String getLocalizedPattern(FacesContext context, String pattern, DecimalFormatSymbols dfs)
785   {
786     if (pattern == null)
787       return null;
788     
789     RequestContext reqCtx = RequestContext.getCurrentInstance();
790     String type = getType();
791     Locale locale = _getLocale(reqCtx, context);
792     DecimalFormat df = (DecimalFormat) _getNumberFormat(pattern, type, locale, reqCtx);
793     if (dfs == null)
794     {
795       dfs = df.getDecimalFormatSymbols();
796     }
797     
798     // If grouping and decimal separator have been customized then
799     // show them in the hint so that it's less confusing for the user.
800     char decSep = dfs.getDecimalSeparator();
801     char groupSep = dfs.getGroupingSeparator();
802 
803     char[] patternArr = pattern.toCharArray();
804     for (int i = 0; i < patternArr.length; i++)
805     {
806       char c = patternArr[i];
807       if (c == '\u002E')
808         patternArr[i] = decSep;
809       else if (c == '\u002C')
810         patternArr[i] = groupSep;
811     }
812     pattern = new String(patternArr);
813     
814     // If the pattern contains the generic currency sign, replace it with the localized 
815     // currency symbol (if one exists), so that when the pattern is displayed (such as in an error 
816     // message), it is more meaningful to the user.
817     // If the pattern contains double international currency symbol, replace it with the international currency symbol. 
818     // For an explanation of this behavior, see section "Special Pattern Characters" at: 
819     // http://java.sun.com/javase/6/docs/api/java/text/DecimalFormat.html
820     // The unicode for the international currency symbol is: \u00A4
821     // The xml hex is        : &#xA4;
822     int idx = pattern.indexOf('\u00A4');
823     if (idx == -1)
824       return pattern;
825     
826     if (idx + 1 < pattern.length() && pattern.charAt(idx + 1) == '\u00A4')
827     {
828       // Matcher.quoteReplacement ensures that the replacement string is properly escaped.
829       String symbol = dfs.getInternationalCurrencySymbol();
830       if (symbol.length() > 0)
831         pattern = pattern.replaceAll(new String(new char[] {'\u00A4', '\u00A4'}), Matcher.quoteReplacement(symbol));
832     }
833     else
834     {
835       // Matcher.quoteReplacement ensures that the replacement string is properly escaped.
836       String symbol = dfs.getCurrencySymbol();
837       if (symbol.length() > 0)
838         pattern = pattern.replaceAll(new String(new char[] {'\u00A4'}), Matcher.quoteReplacement(symbol));
839     }
840     
841     return pattern;
842   }
843 
844   @Override
845   public void setType(String type)
846   {
847     _facesBean.setProperty(_TYPE_KEY, type);
848   }
849 
850   @JSFProperty(defaultValue="number")
851   @Override
852   public String getType()
853   {
854     Object type = _facesBean.getProperty(_TYPE_KEY);
855     return ComponentUtils.resolveString(type, "number");
856   }
857 
858   /**
859    * Returns the hash code for this Converter.
860    */
861   @Override
862   public int hashCode()
863   {
864     int result = 17;
865     result = result * 37 + _getHashValue(getLocale());
866     result = result * 37 + _getHashValue(getCurrencyCode());
867     result = result * 37 + _getHashValue(getCurrencySymbol());
868     result = result * 37 + _getHashValue(getType());
869     result = result * 37 + _getHashValue(getPattern());
870     result = result * 37 + getMaxFractionDigits();
871     result = result * 37 + getMaxIntegerDigits();
872     result = result * 37 + getMinFractionDigits();
873     result = result * 37 + getMinIntegerDigits();
874     result = result * 37 + (isDisabled() ? 1 : 0);    
875     result = result * 37 + (isGroupingUsed() ? 1: 0);
876     result = result * 37 + (isIntegerOnly()? 1: 0);
877     result = result * 37 + (isTransient() ? 1: 0);
878     result = result * 37 + _getHashValue(getMessageDetailConvertPattern());
879     result = result * 37 + _getHashValue(getMessageDetailConvertNumber());
880     result = result * 37 + _getHashValue(getMessageDetailConvertCurrency());
881     result = result * 37 + _getHashValue(getMessageDetailConvertPercent());
882     return result;
883   }
884 
885   /**
886    * <p>Compares this NumberConverter with the specified Object for equality.</p>
887    */
888   @Override
889   public boolean equals(Object numberConverter)
890   {
891     if (this == numberConverter)
892       return true;
893 
894     if (numberConverter instanceof NumberConverter)
895     {
896 
897       NumberConverter nConv = (NumberConverter) numberConverter;
898 
899       return
900         getMaxFractionDigits() == nConv.getMaxFractionDigits()  &&
901         getMaxIntegerDigits()  == nConv.getMaxIntegerDigits()   &&
902         getMinFractionDigits() ==  nConv.getMinFractionDigits() &&
903         getMinIntegerDigits()  ==  nConv.getMinIntegerDigits()  &&
904         isDisabled() == nConv.isDisabled() &&        
905         isTransient() == nConv.isTransient() &&
906         isGroupingUsed() ==  nConv.isGroupingUsed() &&
907         isIntegerOnly()  == nConv.isIntegerOnly()   &&
908         ConverterUtils.equals(getType(), nConv.getType()) &&
909         ConverterUtils.equals(getLocale(), nConv.getLocale()) &&
910         ConverterUtils.equals(getCurrencyCode(), nConv.getCurrencyCode()) &&
911         ConverterUtils.equals(getCurrencySymbol(), nConv.getCurrencySymbol()) &&
912         ConverterUtils.equals(getPattern(), nConv.getPattern()) &&
913         ConverterUtils.equals(getMessageDetailConvertPattern(),
914                               nConv.getMessageDetailConvertPattern()) &&
915         ConverterUtils.equals(getMessageDetailConvertNumber(),
916                               nConv.getMessageDetailConvertNumber())  &&
917         ConverterUtils.equals(getMessageDetailConvertCurrency(),
918                               nConv.getMessageDetailConvertCurrency())&&
919         ConverterUtils.equals(getMessageDetailConvertPercent(),
920                               nConv.getMessageDetailConvertPercent());
921 
922     }
923     return false;
924   }
925 
926   /**
927    * <p>Set the value to property <code>disabled</code>. Default value is false.</p>
928    * @param isDisabled <code>true</code> if it's disabled, <code>false</code> otherwise.
929    */  
930   public void setDisabled(boolean isDisabled)
931   {
932     _facesBean.setProperty(_DISABLED_KEY, Boolean.valueOf(isDisabled));
933   }
934 
935   /**
936     * Return whether it is disabled.
937     * @return true if it's disabled and false if it's enabled. 
938     */
939   public boolean isDisabled()
940   {
941     Boolean disabled = (Boolean) _facesBean.getProperty(_DISABLED_KEY);
942     
943     return (disabled != null) ? disabled.booleanValue() : false;
944   }  
945 
946   private static int _getHashValue(Object obj)
947   {
948     return obj == null? 0 : obj.hashCode();
949   }
950 
951   private static DecimalFormatSymbols _getCachedDecimalFormatSymbol(Locale locale)
952   {
953     synchronized(_SYMBOLS_LOCK)
954     {
955 
956       Object dfs = _patternFormatSymbolsHolder.get(locale);
957 
958       // make sure to return a clone so that it can be mutated at the point
959       // of use based on the setting in the RequestContext
960       if (dfs != null)
961         return (DecimalFormatSymbols) ((DecimalFormatSymbols) dfs).clone();
962       else
963         return null;
964     }
965   }
966 
967   private static void _cacheDecimalFormatSymbols(
968     Locale locale,
969     DecimalFormatSymbols symbols)
970   {
971     synchronized(_SYMBOLS_LOCK)
972     {
973       // -= Simon Lessard =- That if looks paranoid, the map get instanciated 
974       //                     during static initialization and is never set to 
975       //                     null
976        if (_patternFormatSymbolsHolder == null)
977         _patternFormatSymbolsHolder = new HashMap<Locale, DecimalFormatSymbols>();
978        else
979        // to clone here or at the point of creation??
980       // FIXME: Is that line really supposed to go in the else????
981       _patternFormatSymbolsHolder.put(locale, (DecimalFormatSymbols)symbols.clone());
982     }
983   }
984 
985   private static Boolean _getBooleanValue(boolean value)
986   {
987     return (value ? Boolean.TRUE : Boolean.FALSE);
988   }
989 
990   private static Integer _getIntValue(int value)
991   {
992     return Integer.valueOf(value);
993   }
994 
995   private NumberFormat _getCachedNumberFormat(
996     String pattern,
997     String type,
998     Locale locale)
999   {
1000     synchronized(_TYPE_LOCK)
1001     {
1002       // get the map for the appropriate type('number','currency', 'percent') or
1003       // based on diff patterns - pattern1.. pattern2 for diff locales.
1004       String key = ((pattern != null) ? pattern : type);
1005       Map<Locale, NumberFormat> nfMap = _numberFormatHolder.get(key);
1006 
1007       if (nfMap == null)
1008         return null;
1009       else
1010       {
1011         NumberFormat nf = nfMap.get(locale);
1012         if (nf != null)
1013           return (NumberFormat) nf.clone();
1014       }
1015       return null;
1016     }
1017   }
1018 
1019   private void _cacheNumberFormat(
1020     NumberFormat format,
1021     String pattern,
1022     String type,
1023     Locale locale)
1024   {
1025     synchronized(_TYPE_LOCK)
1026     {
1027       // -= Simon Lessard =- That if looks paranoid, the map get instanciated 
1028       //                     during static initialization and is never set to 
1029       //                     null
1030       if (_numberFormatHolder == null)
1031         _numberFormatHolder = new HashMap<String, Map<Locale, NumberFormat>>();
1032 
1033       else
1034       {
1035         // The key could have either been the type based on which formats are
1036         // stored or it can be based on the pattern also.
1037         String key = ((pattern != null) ? pattern : type);
1038 
1039         Map<Locale, NumberFormat> nfMap = _numberFormatHolder.get(key);
1040 
1041         // if we have not cached any NumberFormat for this type, then create a
1042         // map for that type and add to it based on the locale
1043         if (nfMap == null)
1044         {
1045           nfMap = new HashMap<Locale, NumberFormat>();
1046           _numberFormatHolder.put(key, nfMap);
1047           
1048         }
1049         // add this based on the type ('number','currency','percent') or
1050         // pattern1, pattern2.. patternN to the main holder
1051         nfMap.put(locale, (NumberFormat)format.clone());
1052       }
1053     }
1054   }
1055 
1056   private NumberFormat _getNumberFormat(
1057     String pattern,
1058     String type,
1059     Locale locale,
1060     RequestContext reqCtx
1061     )
1062   {
1063     NumberFormat nfmt;
1064 
1065     int formatType = _getType(pattern, type);
1066 
1067     nfmt = _getCachedNumberFormat(pattern, type, locale);
1068 
1069     if (nfmt == null)
1070     {
1071       nfmt = _getNumberFormatter(formatType, pattern, locale);
1072 
1073      _cacheNumberFormat(nfmt,pattern, type, locale);
1074     }
1075 
1076     if (nfmt instanceof DecimalFormat)
1077     {
1078       DecimalFormat dfmt = (DecimalFormat)nfmt;
1079 
1080       // what we get here is a shallow copy. cloned DFS
1081       DecimalFormatSymbols dfSymbols = dfmt.getDecimalFormatSymbols();
1082 
1083       _setUpDecimalSymbolFormatProperties(dfSymbols, reqCtx, locale);
1084 
1085       //since we get a shallow copy - setting it again after modification.
1086       ((DecimalFormat) nfmt).setDecimalFormatSymbols(dfSymbols);
1087     }
1088     else
1089     {
1090       if(_LOG.isWarning())
1091       {
1092         _LOG.warning("Failed to get hold of DecimalFormat for type: +" + type + "\n" +
1093                      "decimal separator,"         +
1094                      "number grouping separator," +
1095                      "currency code"              +
1096                      "will be defaulted based on locale " + locale.toString());
1097      }
1098     }
1099     return nfmt;
1100   }
1101 
1102   private void _setUpDecimalSymbolFormatProperties(
1103     DecimalFormatSymbols symbols,
1104     RequestContext reqCtx,
1105     Locale locale
1106     )
1107   {
1108     if (reqCtx != null)
1109     {
1110       char ch = (char) 0;
1111 
1112       if ((ch = reqCtx.getDecimalSeparator()) != (char)0)
1113         symbols.setDecimalSeparator(ch);
1114 
1115       if ((ch = reqCtx.getNumberGroupingSeparator()) != (char)0)
1116         symbols.setGroupingSeparator(ch);
1117 
1118     }
1119     else
1120     {
1121       if (_LOG.isWarning())
1122       {
1123         _LOG.warning("NULL_REQUESTCONTEXT", locale.toString());
1124         }
1125     }
1126   }
1127 
1128 
1129    // Configure the specified NumberFormat  based on the
1130    // formatting properties that have been set.
1131   private void _setFormatProperties(NumberFormat formatter) {
1132 
1133     formatter.setGroupingUsed(isGroupingUsed());
1134 
1135     if (isMaximumFractionDigitsSet())
1136     {
1137         formatter.setMaximumFractionDigits(getMaxFractionDigits());
1138     }
1139 
1140     if (isMaximumIntegerDigitsSet())
1141     {
1142       formatter.setMaximumIntegerDigits(getMaxIntegerDigits());
1143     }
1144 
1145     if (isMinimumFractionDigitsSet())
1146     {
1147       formatter.setMinimumFractionDigits(getMinFractionDigits());
1148     }
1149 
1150     if (isMinimumIntegerDigitsSet())
1151     {
1152       formatter.setMinimumIntegerDigits(getMinIntegerDigits());
1153     }
1154   }
1155 
1156   private void _setCurrencyInformation(
1157     RequestContext context,
1158     DecimalFormatSymbols symbols)
1159   {
1160     String currencyCode = _getCurrencyCode(context);
1161 
1162     // currencyCode is set we honour currency code.
1163     if (currencyCode != null)
1164     {
1165       symbols.setCurrency(Currency.getInstance(currencyCode));
1166       return;
1167     }
1168 
1169     if (getCurrencySymbol() != null)
1170     {
1171       symbols.setCurrencySymbol(getCurrencySymbol());
1172 
1173        // Loggin at level INFO - shows up by default - so use fine.
1174       _LOG.fine("Using currency symbol as currecny code evaluates to null");
1175     }
1176     // currency symbol will now default based on the locale.
1177   }
1178 
1179   private NumberFormat _getNumberFormatter(
1180     int formatType,
1181     String pattern,
1182     Locale locale)
1183   {
1184     NumberFormat nfmt;
1185 
1186     if(_PATTERN_TYPE == formatType)
1187     {
1188       DecimalFormatSymbols symbols = _getCachedDecimalFormatSymbol(locale);
1189 
1190       if (null == symbols)
1191       {
1192          symbols = new DecimalFormatSymbols(locale);
1193          // cache this - It is cloned while caching.
1194         _cacheDecimalFormatSymbols(locale, symbols);
1195       }
1196       nfmt = new DecimalFormat(pattern, symbols);
1197     }
1198 
1199     else if(_NUMBER_TYPE == formatType)
1200     {
1201        nfmt = NumberFormat.getNumberInstance(locale);
1202     }
1203     else if(_CURRENCY_TYPE == formatType)
1204     {
1205       nfmt = NumberFormat.getCurrencyInstance(locale);
1206     }
1207     else if(_PERCENT_TYPE == formatType)
1208     {
1209       nfmt = NumberFormat.getPercentInstance(locale);
1210     }
1211     else
1212     {
1213       // never expected to happen
1214       assert (formatType > _PATTERN_TYPE || formatType < _NUMBER_TYPE) : "invalid type" ;
1215       nfmt = null;
1216     }
1217     return nfmt;
1218   }
1219 
1220   private Object _getRawConvertCurrencyMessageDetail()
1221   {
1222     return _facesBean.getRawProperty(_CONVERT_CURRENCY_MESSAGE_DETAIL_KEY);
1223   }
1224 
1225   private Object _getRawConvertNumberMessageDetail()
1226   {
1227     return _facesBean.getRawProperty(_CONVERT_NUMBER_MESSAGE_DETAIL_KEY);
1228   }
1229 
1230   private Object _getRawConvertPatternMessageDetail()
1231   {
1232     return _facesBean.getRawProperty(_CONVERT_PATTERN_MESSAGE_DETAIL_KEY);
1233   }
1234 
1235   private Object _getRawConvertPercentMessageDetail()
1236   {
1237     return _facesBean.getRawProperty(_CONVERT_PERCENT_MESSAGE_DETAIL_KEY);
1238   }
1239 
1240   protected final FacesMessage getConvertMessage(
1241     FacesContext context,
1242     UIComponent component,
1243     String inputValue,
1244     Object[] params
1245     )
1246   {
1247     int type = _getType(getPattern(), getType());
1248     Object convMsgDet;
1249     String msgId;
1250 
1251     // Always check for pattern first.
1252     if (_PATTERN_TYPE == type)
1253     {
1254       convMsgDet = _getRawConvertPatternMessageDetail();
1255       msgId = CONVERT_PATTERN_MESSAGE_ID;
1256     }
1257     else if(_NUMBER_TYPE == type)
1258     {
1259       convMsgDet = _getRawConvertNumberMessageDetail();
1260       msgId = CONVERT_NUMBER_MESSAGE_ID;
1261     }
1262     else if(_CURRENCY_TYPE == type)
1263     {
1264       convMsgDet = _getRawConvertCurrencyMessageDetail();
1265       msgId = CONVERT_CURRENCY_MESSAGE_ID;
1266     }
1267     else if(_PERCENT_TYPE == type)
1268     {
1269       convMsgDet = _getRawConvertPercentMessageDetail();
1270       msgId = CONVERT_PERCENT_MESSAGE_ID;
1271     }
1272     else
1273     {
1274       throw new IllegalArgumentException("Invalid type: " + getType());
1275     }
1276     
1277     return MessageFactory.getMessage(context, 
1278                                      msgId, 
1279                                      convMsgDet, 
1280                                      params, 
1281                                      component);
1282   }
1283 
1284   private Locale _getLocale(RequestContext rc, FacesContext context)
1285   {
1286     Locale locale = getLocale();
1287     if (locale == null )
1288     {
1289       locale = rc.getFormattingLocale();
1290       if (locale == null)
1291       {
1292         locale = context.getViewRoot().getLocale();
1293       }
1294     }
1295 
1296     return locale;
1297   }
1298 
1299   private String _getCurrencyCode(
1300     RequestContext context
1301     )
1302   {
1303     String currencyCode = getCurrencyCode();
1304     if (currencyCode == null)
1305     {
1306       if (context != null)
1307       {
1308         currencyCode = context.getCurrencyCode();
1309       }
1310       else
1311       {
1312         _LOG.warning("NULL_REQUEST_CONTEXT_UNABLE_GET_CURRENCY_CODE");
1313       }
1314     }
1315 
1316     return currencyCode;
1317   }
1318 
1319   // applied only while formatting
1320   private void _setCurrencyFormattingProperties(
1321     RequestContext context,
1322     NumberFormat numberFormatter
1323    )
1324   {
1325     // Useless if... should be instanceof DecimalFormat
1326     // Potential ClassCastException before the change
1327     //if (numberFormatter instanceof NumberFormat)
1328     if (numberFormatter instanceof DecimalFormat)
1329     {
1330       DecimalFormat dfmt = (DecimalFormat)numberFormatter;
1331       DecimalFormatSymbols symbols = dfmt.getDecimalFormatSymbols();
1332       _setCurrencyInformation(context, symbols);
1333       dfmt.setDecimalFormatSymbols(symbols);
1334     }
1335     else
1336     {  //string cat at compile time.
1337       _LOG.warning("NUMBER_NOT_DECIMALFORMAT_IGNORE_CURRENCY");
1338     }
1339   }
1340 
1341   private static int _getType(String pattern, String type)
1342   {
1343     // check for pattern should be done first.
1344     if (pattern != null)
1345       return _PATTERN_TYPE;
1346 
1347     else if ("number".equals(type))
1348      return _NUMBER_TYPE;
1349 
1350     else if ("currency".equals(type))
1351      return _CURRENCY_TYPE;
1352 
1353     else if ("percent".equals(type))
1354      return _PERCENT_TYPE;
1355     else
1356       throw new IllegalArgumentException(_LOG.getMessage(
1357         "NOT_VALID_TYPE", type));
1358   }
1359  
1360  /**
1361    * Return true if the maximum fraction digits have been set. If not set, return false;
1362    * @return true, if the maximum fraction digits have been set. 
1363    */
1364   public boolean isMaximumFractionDigitsSet()
1365   {
1366     return _facesBean.getProperty(_MAX_FRACTION_DIGITS_KEY) != null;
1367   }
1368 
1369  /**
1370    * Return true if the minimum fraction digits have been set. If not set, return false;
1371    * @return true, if the minimum fraction digits have been set. 
1372    */
1373   public boolean isMinimumFractionDigitsSet()
1374   {
1375     return _facesBean.getProperty(_MIN_FRACTION_DIGITS_KEY) != null;
1376   }
1377 
1378  /**
1379    * Return true if the maximum integer digits have been set. If not set, return false;
1380    * @return true, if the maximum integer digits have been set. 
1381    */
1382   public boolean isMaximumIntegerDigitsSet()
1383   {
1384     return _facesBean.getProperty(_MAX_INTEGER_DIGITS_KEY) != null;
1385   }
1386 
1387  /**
1388    * Return true if the minimum integer digits have been set. If not set, return false;
1389    * @return true, if the minimum integer digits have been set. 
1390    */
1391   public boolean isMinimumIntegerDigitsSet()
1392   {
1393     return _facesBean.getProperty(_MIN_INTEGER_DIGITS_KEY) != null;
1394   }
1395 
1396   private static final FacesBean.Type _TYPE = new FacesBean.Type();
1397 
1398   private static final PropertyKey _CONVERT_CURRENCY_MESSAGE_DETAIL_KEY
1399    = _TYPE.registerKey("messageDetailConvertCurrency", String.class);
1400 
1401   private static final PropertyKey _CONVERT_NUMBER_MESSAGE_DETAIL_KEY
1402    = _TYPE.registerKey("messageDetailConvertNumber", String.class);
1403 
1404   private static final PropertyKey _CONVERT_PATTERN_MESSAGE_DETAIL_KEY
1405    = _TYPE.registerKey("messageDetailConvertPattern", String.class);
1406 
1407   private static final PropertyKey _CONVERT_PERCENT_MESSAGE_DETAIL_KEY
1408    = _TYPE.registerKey("messageDetailConvertPercent", String.class);
1409   
1410   private static final PropertyKey  _HINT_PATTERN_KEY =
1411     _TYPE.registerKey("hintPattern", String.class);
1412 
1413   private static final PropertyKey _CURRENCY_CODE_KEY
1414    = _TYPE.registerKey("currencyCode", String.class);
1415 
1416   private static final PropertyKey _CURRENCY_SYMBOL_KEY
1417    = _TYPE.registerKey("currencySymbol", String.class);
1418 
1419   private static final PropertyKey _GROUPING_USED_KEY
1420    = _TYPE.registerKey("groupingUsed", boolean.class, Boolean.TRUE);
1421 
1422   private static final PropertyKey _INTEGER_ONLY_KEY
1423    =  _TYPE.registerKey("integerOnly", boolean.class, Boolean.FALSE);
1424 
1425   private static final PropertyKey _LOCALE_KEY
1426    = _TYPE.registerKey("locale", Locale.class);
1427 
1428   private static final PropertyKey _MAX_FRACTION_DIGITS_KEY
1429    =  _TYPE.registerKey("maxFractionDigits", int.class);
1430 
1431   private static final PropertyKey _MAX_INTEGER_DIGITS_KEY
1432    = _TYPE.registerKey("maxIntegerDigits", int.class);
1433 
1434   private static final PropertyKey _MIN_FRACTION_DIGITS_KEY
1435    = _TYPE.registerKey("minFractionDigits", int.class);
1436 
1437   private static final PropertyKey _MIN_INTEGER_DIGITS_KEY
1438    = _TYPE.registerKey("minIntegerDigits", int.class);
1439 
1440   private static final PropertyKey _PATTERN_KEY
1441    = _TYPE.registerKey("pattern", String.class);
1442 
1443   private static final PropertyKey  _TYPE_KEY
1444    = _TYPE.registerKey("type", String.class, "numeric");
1445   
1446   // Default is false
1447   private static final PropertyKey _DISABLED_KEY =
1448     _TYPE.registerKey("disabled", Boolean.class, Boolean.FALSE);
1449 
1450   private FacesBean _facesBean = ConverterUtils.getFacesBean(_TYPE);
1451 
1452   private static TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(NumberConverter.class);
1453 
1454 
1455   // This is a Map which will hold type and patterns as the key and then
1456   // hold corresponding  number formats based on the locale. The keys of this
1457   // map are 'number', 'percent', 'currency' and differernt patterns which the
1458   // user could have used during the entire application. Each key will
1459   // hold a Map as its value.
1460   // This Map (value part of the type) will hold locale as its key and Number
1461   // formats as its values.
1462   private static Map<String, Map<Locale, NumberFormat>> _numberFormatHolder = 
1463     new HashMap<String, Map<Locale, NumberFormat>>();
1464 
1465   // This is map to hold DecimalFormatSymbols when the converter is used,
1466   // by specifying a pattern. When a pattern is specified we take care of
1467   // creating the DecimalFormatSymbols. We are caching decimal format symbols
1468   // based on the locale so that we can make use of it, when a new number
1469   // converters is instantiated and used based on pattern and not by type.
1470   private static Map<Locale, DecimalFormatSymbols> _patternFormatSymbolsHolder = 
1471     new HashMap<Locale, DecimalFormatSymbols>();
1472 
1473   private static final Object _TYPE_LOCK = new Object();
1474 
1475   private static final Object _SYMBOLS_LOCK = new Object();
1476 
1477   private static final int _NUMBER_TYPE   = 1;
1478 
1479   private static final int _CURRENCY_TYPE = 2;
1480 
1481   private static final int _PERCENT_TYPE  = 3;
1482 
1483   private static final int _PATTERN_TYPE  = 4;
1484   
1485   private static final Number _EXAMPLE_PERCENT;
1486 
1487   private static final Number _EXAMPLE_CURRENCY;
1488 
1489   static
1490   {
1491     _EXAMPLE_PERCENT = 0.3423d;
1492     _EXAMPLE_CURRENCY = 10250;
1493   }
1494 }