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