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 javax.faces.convert;
20  
21  import java.math.BigDecimal;
22  import java.math.BigInteger;
23  import java.text.DecimalFormat;
24  import java.text.DecimalFormatSymbols;
25  import java.text.NumberFormat;
26  import java.text.ParseException;
27  import java.util.Currency;
28  import java.util.Locale;
29  
30  import javax.el.ValueExpression;
31  import javax.faces.component.PartialStateHolder;
32  import javax.faces.component.UIComponent;
33  import javax.faces.context.FacesContext;
34  
35  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFConverter;
36  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFJspProperty;
37  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
38  
39  /**
40   * This tag creates a number formatting converter and associates it
41   * with the nearest parent UIComponent.
42   * 
43   * Unless otherwise specified, all attributes accept static values or EL expressions.
44   * 
45   * see Javadoc of <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a>
46   */
47  @JSFConverter(
48      name="f:convertNumber",
49      bodyContent="empty",
50      tagClass="org.apache.myfaces.taglib.core.ConvertNumberTag")
51  @JSFJspProperty(
52      name="binding", 
53      returnType = "javax.faces.convert.NumberConverter",
54      longDesc = "A ValueExpression that evaluates to a NumberConverter.")
55  public class NumberConverter
56          implements Converter, PartialStateHolder
57  {
58      // API FIELDS
59      public static final String CONVERTER_ID = "javax.faces.Number";
60      public static final String STRING_ID = "javax.faces.converter.STRING";
61      public static final String CURRENCY_ID = "javax.faces.converter.NumberConverter.CURRENCY";
62      public static final String NUMBER_ID = "javax.faces.converter.NumberConverter.NUMBER";
63      public static final String PATTERN_ID = "javax.faces.converter.NumberConverter.PATTERN";
64      public static final String PERCENT_ID = "javax.faces.converter.NumberConverter.PERCENT";
65  
66      private String _currencyCode;
67      private String _currencySymbol;
68      private Locale _locale;
69      private int _maxFractionDigits;
70      private int _maxIntegerDigits;
71      private int _minFractionDigits;
72      private int _minIntegerDigits;
73      private String _pattern;
74      private String _type = "number";
75      private boolean _groupingUsed = true;
76      private boolean _integerOnly = false;
77      private boolean _transient;
78  
79      private boolean _maxFractionDigitsSet;
80      private boolean _maxIntegerDigitsSet;
81      private boolean _minFractionDigitsSet;
82      private boolean _minIntegerDigitsSet;
83  
84  
85      // CONSTRUCTORS
86      public NumberConverter()
87      {
88      }
89  
90      // METHODS
91      public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value)
92      {
93          if (facesContext == null)
94          {
95              throw new NullPointerException("facesContext");
96          }
97          if (uiComponent == null)
98          {
99              throw new NullPointerException("uiComponent");
100         }
101 
102         if (value != null)
103         {
104             value = value.trim();
105             if (value.length() > 0)
106             {
107                 NumberFormat format = getNumberFormat(facesContext);
108                 format.setParseIntegerOnly(_integerOnly);
109                 
110                 DecimalFormat df = (DecimalFormat)format;
111                 
112                 // The best we can do in this case is check if there is a ValueExpression
113                 // with a BigDecimal as returning type , and if that so enable BigDecimal parsing
114                 // to prevent loss in precision, and do not break existing examples (since
115                 // in those cases it is expected to return Double). See MYFACES-1890 and TRINIDAD-1124
116                 // for details
117                 ValueExpression valueBinding = uiComponent.getValueExpression("value");
118                 Class<?> destType = null;
119                 if (valueBinding != null)
120                 {
121                     destType = valueBinding.getType(facesContext.getELContext());
122                     if (destType != null
123                         && (BigDecimal.class.isAssignableFrom(destType) || BigInteger.class.isAssignableFrom(destType)))
124                     {
125                         df.setParseBigDecimal(true);
126                     }
127                 }
128                 
129                 DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
130                 boolean changed = false;
131                 if(dfs.getGroupingSeparator() == '\u00a0')
132                 {
133                   dfs.setGroupingSeparator(' ');
134                   df.setDecimalFormatSymbols(dfs);
135                   value = value.replace('\u00a0', ' ');
136                   changed = true;
137                 }
138                 
139                 formatCurrency(format);
140                 
141                 try
142                 {
143                     return parse(value, format, destType);
144                 }
145                 catch (ParseException e)
146                 {
147                   if(changed)
148                   {
149                     dfs.setGroupingSeparator('\u00a0');
150                     df.setDecimalFormatSymbols(dfs);
151                   }
152                   try
153                   {
154                       return parse(value, format, destType);
155                   }
156                   catch (ParseException pe)
157                   {
158 
159                     if(getPattern() != null)
160                     {
161                         throw new ConverterException(_MessageUtils.getErrorMessage(facesContext,
162                                 PATTERN_ID,
163                                 new Object[]{value, "$###,###", _MessageUtils.getLabel(facesContext, uiComponent)}));
164                     }
165                     else if(getType().equals("number"))
166                     {
167                         throw new ConverterException(_MessageUtils.getErrorMessage(facesContext,
168                                 NUMBER_ID,
169                                 new Object[]{value, format.format(21),
170                                              _MessageUtils.getLabel(facesContext, uiComponent)}));
171                     }
172                     else if(getType().equals("currency"))
173                     {
174                         throw new ConverterException(_MessageUtils.getErrorMessage(facesContext,
175                                 CURRENCY_ID,
176                                 new Object[]{value, format.format(42.25),
177                                              _MessageUtils.getLabel(facesContext, uiComponent)}));
178                     }
179                     else if(getType().equals("percent"))
180                     {
181                         throw new ConverterException(_MessageUtils.getErrorMessage(facesContext,
182                                 PERCENT_ID,
183                                 new Object[]{value, format.format(.90),
184                                              _MessageUtils.getLabel(facesContext, uiComponent)}));
185                     }
186                   }
187                 }
188             }
189         }
190         return null;
191     }
192 
193     private Object parse(String value, NumberFormat format, Class<?> destType)
194         throws ParseException
195     {
196         if (destType == BigInteger.class)
197         {
198             return ((BigDecimal) format.parse(value)).toBigInteger();
199         }
200         else
201         {
202             return format.parse(value);
203         }
204     }
205 
206     public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value)
207     {
208         if (facesContext == null)
209         {
210             throw new NullPointerException("facesContext");
211         }
212         if (uiComponent == null)
213         {
214             throw new NullPointerException("uiComponent");
215         }
216 
217         if (value == null)
218         {
219             return "";
220         }
221         if (value instanceof String)
222         {
223             return (String)value;
224         }
225 
226         NumberFormat format = getNumberFormat(facesContext);
227         format.setGroupingUsed(_groupingUsed);
228         if (_maxFractionDigitsSet)
229         {
230             format.setMaximumFractionDigits(_maxFractionDigits);
231         }
232         if (_maxIntegerDigitsSet)
233         {
234             format.setMaximumIntegerDigits(_maxIntegerDigits);
235         }
236         if (_minFractionDigitsSet)
237         {
238             format.setMinimumFractionDigits(_minFractionDigits);
239         }
240         if (_minIntegerDigitsSet)
241         {
242             format.setMinimumIntegerDigits(_minIntegerDigits);
243         }
244         formatCurrency(format);
245         try
246         {
247             return format.format(value);
248         }
249         catch (Exception e)
250         {
251             throw new ConverterException(_MessageUtils.getErrorMessage(facesContext, STRING_ID,
252                     new Object[]{value,_MessageUtils.getLabel(facesContext, uiComponent)}),e);
253         }
254     }
255 
256     private NumberFormat getNumberFormat(FacesContext facesContext)
257     {
258         Locale locale = _locale != null ? _locale : facesContext.getViewRoot().getLocale();
259 
260         if (_pattern == null && _type == null)
261         {
262             throw new ConverterException("Cannot get NumberFormat, either type or pattern needed.");
263         }
264 
265         // pattern
266         if (_pattern != null)
267         {
268             return new DecimalFormat(_pattern, new DecimalFormatSymbols(locale));
269         }
270 
271         // type
272         if (_type.equals("number"))
273         {
274             return NumberFormat.getNumberInstance(locale);
275         }
276         else if (_type.equals("currency"))
277         {
278             return NumberFormat.getCurrencyInstance(locale);
279         }
280         else if (_type.equals("percent"))
281         {
282             return NumberFormat.getPercentInstance(locale);
283         }
284         throw new ConverterException("Cannot get NumberFormat, illegal type " + _type);
285     }
286 
287     private void formatCurrency(NumberFormat format)
288     {
289         if (_currencyCode == null && _currencySymbol == null)
290         {
291             return;
292         }
293 
294         boolean useCurrencyCode;
295         useCurrencyCode = _currencyCode != null;
296 
297         if (useCurrencyCode)
298         {
299             // set Currency
300             try
301             {
302                 format.setCurrency(Currency.getInstance(_currencyCode));
303             }
304             catch (Exception e)
305             {
306                 throw new ConverterException("Unable to get Currency instance for currencyCode " +
307                                              _currencyCode);
308             }
309         }
310         else if (format instanceof DecimalFormat)
311 
312         {
313             DecimalFormat dFormat = (DecimalFormat)format;
314             DecimalFormatSymbols symbols = dFormat.getDecimalFormatSymbols();
315             symbols.setCurrencySymbol(_currencySymbol);
316             dFormat.setDecimalFormatSymbols(symbols);
317         }
318     }
319 
320     // STATE SAVE/RESTORE
321     public void restoreState(FacesContext facesContext, Object state)
322     {
323         if (state != null)
324         {
325             Object values[] = (Object[])state;
326             _currencyCode = (String)values[0];
327             _currencySymbol = (String)values[1];
328             _locale = (Locale)values[2];
329             Integer value = (Integer)values[3];
330             _maxFractionDigits = value != null ? value.intValue() : 0;
331             value = (Integer)values[4];
332             _maxIntegerDigits = value != null ? value.intValue() : 0;
333             value = (Integer)values[5];
334             _minFractionDigits = value != null ? value.intValue() : 0;
335             value = (Integer)values[6];
336             _minIntegerDigits = value != null ? value.intValue() : 0;
337             _pattern = (String)values[7];
338             _type = (String)values[8];
339             _groupingUsed = ((Boolean)values[9]).booleanValue();
340             _integerOnly = ((Boolean)values[10]).booleanValue();
341             _maxFractionDigitsSet = ((Boolean)values[11]).booleanValue();
342             _maxIntegerDigitsSet = ((Boolean)values[12]).booleanValue();
343             _minFractionDigitsSet = ((Boolean)values[13]).booleanValue();
344             _minIntegerDigitsSet = ((Boolean)values[14]).booleanValue();
345         }
346     }
347 
348     public Object saveState(FacesContext facesContext)
349     {
350         if (!initialStateMarked())
351         {
352             Object values[] = new Object[15];
353             values[0] = _currencyCode;
354             values[1] = _currencySymbol;
355             values[2] = _locale;
356             values[3] = _maxFractionDigitsSet ? Integer.valueOf(_maxFractionDigits) : null;
357             values[4] = _maxIntegerDigitsSet ? Integer.valueOf(_maxIntegerDigits) : null;
358             values[5] = _minFractionDigitsSet ? Integer.valueOf(_minFractionDigits) : null;
359             values[6] = _minIntegerDigitsSet ? Integer.valueOf(_minIntegerDigits) : null;
360             values[7] = _pattern;
361             values[8] = _type;
362             values[9] = _groupingUsed ? Boolean.TRUE : Boolean.FALSE;
363             values[10] = _integerOnly ? Boolean.TRUE : Boolean.FALSE;
364             values[11] = _maxFractionDigitsSet ? Boolean.TRUE : Boolean.FALSE;
365             values[12] = _maxIntegerDigitsSet ? Boolean.TRUE : Boolean.FALSE;
366             values[13] = _minFractionDigitsSet ? Boolean.TRUE : Boolean.FALSE;
367             values[14] = _minIntegerDigitsSet ? Boolean.TRUE : Boolean.FALSE;
368             return values;
369         }
370         return null;
371     }
372 
373     // GETTER & SETTER
374     
375     /**
376      * ISO 4217 currency code
377      * 
378      */
379     @JSFProperty
380     public String getCurrencyCode()
381     {
382         return _currencyCode != null ?
383                _currencyCode :
384                getDecimalFormatSymbols().getInternationalCurrencySymbol();
385     }
386 
387     public void setCurrencyCode(String currencyCode)
388     {
389         _currencyCode = currencyCode;
390         clearInitialState();
391     }
392 
393     /**
394      * The currency symbol used to format a currency value.  Defaults
395      * to the currency symbol for locale.
396      * 
397      */
398     @JSFProperty
399     public String getCurrencySymbol()
400     {
401         return _currencySymbol != null ?
402                _currencySymbol :
403                getDecimalFormatSymbols().getCurrencySymbol();
404     }
405 
406     public void setCurrencySymbol(String currencySymbol)
407     {
408         _currencySymbol = currencySymbol;
409         clearInitialState();
410     }
411 
412     /**
413      * Specifies whether output will contain grouping separators.  Default: true.
414      * 
415      */
416     @JSFProperty(deferredValueType="java.lang.Boolean")
417     public boolean isGroupingUsed()
418     {
419         return _groupingUsed;
420     }
421 
422     public void setGroupingUsed(boolean groupingUsed)
423     {
424         _groupingUsed = groupingUsed;
425         clearInitialState();
426     }
427 
428     /**
429      * Specifies whether only the integer part of the input will be parsed.  Default: false.
430      * 
431      */
432     @JSFProperty(deferredValueType="java.lang.Boolean")
433     public boolean isIntegerOnly()
434     {
435         return _integerOnly;
436     }
437 
438     public void setIntegerOnly(boolean integerOnly)
439     {
440         _integerOnly = integerOnly;
441         clearInitialState();
442     }
443 
444     /**
445      * The name of the locale to be used, instead of the default as
446      * specified in the faces configuration file.
447      * 
448      */
449     @JSFProperty(deferredValueType="java.lang.Object")
450     public Locale getLocale()
451     {
452         if (_locale != null)
453         {
454             return _locale;
455         }
456         FacesContext context = FacesContext.getCurrentInstance();
457         return context.getViewRoot().getLocale();
458     }
459 
460     public void setLocale(Locale locale)
461     {
462         _locale = locale;
463         clearInitialState();
464     }
465 
466     /**
467      * The maximum number of digits in the fractional portion of the number.
468      * 
469      */
470     @JSFProperty(deferredValueType="java.lang.Integer")
471     public int getMaxFractionDigits()
472     {
473         return _maxFractionDigits;
474     }
475 
476     public void setMaxFractionDigits(int maxFractionDigits)
477     {
478         _maxFractionDigitsSet = true;
479         _maxFractionDigits = maxFractionDigits;
480         clearInitialState();
481     }
482 
483     /**
484      * The maximum number of digits in the integer portion of the number.
485      * 
486      */
487     @JSFProperty(deferredValueType="java.lang.Integer")
488     public int getMaxIntegerDigits()
489     {
490         return _maxIntegerDigits;
491     }
492 
493     public void setMaxIntegerDigits(int maxIntegerDigits)
494     {
495         _maxIntegerDigitsSet = true;
496         _maxIntegerDigits = maxIntegerDigits;
497         clearInitialState();
498     }
499 
500     /**
501      * The minimum number of digits in the fractional portion of the number.
502      * 
503      */
504     @JSFProperty(deferredValueType="java.lang.Integer")
505     public int getMinFractionDigits()
506     {
507         return _minFractionDigits;
508     }
509 
510     public void setMinFractionDigits(int minFractionDigits)
511     {
512         _minFractionDigitsSet = true;
513         _minFractionDigits = minFractionDigits;
514         clearInitialState();
515     }
516 
517     /**
518      * The minimum number of digits in the integer portion of the number.
519      * 
520      */
521     @JSFProperty(deferredValueType="java.lang.Integer")
522     public int getMinIntegerDigits()
523     {
524         return _minIntegerDigits;
525     }
526 
527     public void setMinIntegerDigits(int minIntegerDigits)
528     {
529         _minIntegerDigitsSet = true;
530         _minIntegerDigits = minIntegerDigits;
531         clearInitialState();
532     }
533 
534     /**
535      * A custom Date formatting pattern, in the format used by java.text.SimpleDateFormat.
536      * 
537      */
538     @JSFProperty
539     public String getPattern()
540     {
541         return _pattern;
542     }
543 
544     public void setPattern(String pattern)
545     {
546         _pattern = pattern;
547         clearInitialState();
548     }
549 
550     public boolean isTransient()
551     {
552         return _transient;
553     }
554 
555     public void setTransient(boolean aTransient)
556     {
557         _transient = aTransient;
558     }
559 
560     /**
561      * The type of formatting/parsing to be performed.  Values include:
562      * number, currency, and percent.  Default: number.
563      * 
564      */
565     @JSFProperty
566     public String getType()
567     {
568         return _type;
569     }
570 
571     public void setType(String type)
572     {
573         //TODO: validate type
574         _type = type;
575         clearInitialState();
576     }
577 
578     private DecimalFormatSymbols getDecimalFormatSymbols()
579     {
580         return new DecimalFormatSymbols(getLocale());
581     }
582     
583     private boolean _initialStateMarked = false;
584 
585     public void clearInitialState()
586     {
587         _initialStateMarked = false;
588     }
589 
590     public boolean initialStateMarked()
591     {
592         return _initialStateMarked;
593     }
594 
595     public void markInitialState()
596     {
597         _initialStateMarked = true;
598     }
599 }