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.text.DecimalFormat;
23  import java.text.DecimalFormatSymbols;
24  import java.text.NumberFormat;
25  import java.text.ParseException;
26  import java.util.Currency;
27  import java.util.Locale;
28  
29  import javax.el.ValueExpression;
30  import javax.faces.component.PartialStateHolder;
31  import javax.faces.component.UIComponent;
32  import javax.faces.context.FacesContext;
33  
34  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFConverter;
35  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFJspProperty;
36  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
37  
38  /**
39   * This tag creates a number formatting converter and associates it
40   * with the nearest parent UIComponent.
41   * 
42   * Unless otherwise specified, all attributes accept static values or EL expressions.
43   * 
44   * see Javadoc of <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a>
45   *
46   * @author Thomas Spiegl (latest modification by $Author: lu4242 $)
47   * @version $Revision: 1237538 $ $Date: 2012-01-29 22:43:13 -0500 (Sun, 29 Jan 2012) $
48   */
49  @JSFConverter(
50      name="f:convertNumber",
51      bodyContent="empty",
52      tagClass="org.apache.myfaces.taglib.core.ConvertNumberTag")
53  @JSFJspProperty(
54      name="binding", 
55      returnType = "javax.faces.convert.NumberConverter",
56      longDesc = "A ValueExpression that evaluates to a NumberConverter.")
57  public class NumberConverter
58          implements Converter, PartialStateHolder
59  {
60      // API FIELDS
61      public static final String CONVERTER_ID = "javax.faces.Number";
62      public static final String STRING_ID = "javax.faces.converter.STRING";
63      public static final String CURRENCY_ID = "javax.faces.converter.NumberConverter.CURRENCY";
64      public static final String NUMBER_ID = "javax.faces.converter.NumberConverter.NUMBER";
65      public static final String PATTERN_ID = "javax.faces.converter.NumberConverter.PATTERN";
66      public static final String PERCENT_ID = "javax.faces.converter.NumberConverter.PERCENT";
67  
68      private static final boolean JAVA_VERSION_14;
69  
70      static
71      {
72          JAVA_VERSION_14 = checkJavaVersion14();
73      }
74  
75      private String _currencyCode;
76      private String _currencySymbol;
77      private Locale _locale;
78      private int _maxFractionDigits;
79      private int _maxIntegerDigits;
80      private int _minFractionDigits;
81      private int _minIntegerDigits;
82      private String _pattern;
83      private String _type = "number";
84      private boolean _groupingUsed = true;
85      private boolean _integerOnly = false;
86      private boolean _transient;
87  
88      private boolean _maxFractionDigitsSet;
89      private boolean _maxIntegerDigitsSet;
90      private boolean _minFractionDigitsSet;
91      private boolean _minIntegerDigitsSet;
92  
93  
94      // CONSTRUCTORS
95      public NumberConverter()
96      {
97      }
98  
99      // METHODS
100     public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value)
101     {
102         if (facesContext == null)
103         {
104             throw new NullPointerException("facesContext");
105         }
106         if (uiComponent == null)
107         {
108             throw new NullPointerException("uiComponent");
109         }
110 
111         if (value != null)
112         {
113             value = value.trim();
114             if (value.length() > 0)
115             {
116                 NumberFormat format = getNumberFormat(facesContext);
117                 format.setParseIntegerOnly(_integerOnly);
118                 
119                 DecimalFormat df = (DecimalFormat)format;
120                 
121                 // The best we can do in this case is check if there is a ValueExpression
122                 // with a BigDecimal as returning type , and if that so enable BigDecimal parsing
123                 // to prevent loss in precision, and do not break existing examples (since
124                 // in those cases it is expected to return Double). See MYFACES-1890 and TRINIDAD-1124
125                 // for details
126                 ValueExpression valueBinding = uiComponent.getValueExpression("value");
127                 if (valueBinding != null)
128                 {
129                     Class<?> destType = valueBinding.getType(facesContext.getELContext());
130                     if (destType != null && BigDecimal.class.isAssignableFrom(destType))
131                     {
132                         df.setParseBigDecimal(true);
133                     }
134                 }
135                 
136                 DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
137                 boolean changed = false;
138                 if(dfs.getGroupingSeparator() == '\u00a0')
139                 {
140                   dfs.setGroupingSeparator(' ');
141                   df.setDecimalFormatSymbols(dfs);
142                   changed = true;
143                 }
144                 
145                 formatCurrency(format);
146                 
147                 try
148                 {
149                     return format.parse(value);
150                 }
151                 catch (ParseException e)
152                 {
153                   if(changed)
154                   {
155                     dfs.setGroupingSeparator('\u00a0');
156                     df.setDecimalFormatSymbols(dfs);
157                   }
158                   try
159                   {
160                     return format.parse(value);
161                   }
162                   catch (ParseException pe)
163                   {
164 
165                     if(getPattern() != null)
166                     {
167                         throw new ConverterException(_MessageUtils.getErrorMessage(facesContext,
168                                 PATTERN_ID,
169                                 new Object[]{value, "$###,###", _MessageUtils.getLabel(facesContext, uiComponent)}));
170                     }
171                     else if(getType().equals("number"))
172                     {
173                         throw new ConverterException(_MessageUtils.getErrorMessage(facesContext,
174                                 NUMBER_ID,
175                                 new Object[]{value, format.format(21),
176                                              _MessageUtils.getLabel(facesContext, uiComponent)}));
177                     }
178                     else if(getType().equals("currency"))
179                     {
180                         throw new ConverterException(_MessageUtils.getErrorMessage(facesContext,
181                                 CURRENCY_ID,
182                                 new Object[]{value, format.format(42.25),
183                                              _MessageUtils.getLabel(facesContext, uiComponent)}));
184                     }
185                     else if(getType().equals("percent"))
186                     {
187                         throw new ConverterException(_MessageUtils.getErrorMessage(facesContext,
188                                 PERCENT_ID,
189                                 new Object[]{value, format.format(.90),
190                                              _MessageUtils.getLabel(facesContext, uiComponent)}));
191                     }
192                   }
193                 }
194             }
195         }
196         return null;
197     }
198 
199     public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value)
200     {
201         if (facesContext == null)
202         {
203             throw new NullPointerException("facesContext");
204         }
205         if (uiComponent == null)
206         {
207             throw new NullPointerException("uiComponent");
208         }
209 
210         if (value == null)
211         {
212             return "";
213         }
214         if (value instanceof String)
215         {
216             return (String)value;
217         }
218 
219         NumberFormat format = getNumberFormat(facesContext);
220         format.setGroupingUsed(_groupingUsed);
221         if (_maxFractionDigitsSet)
222         {
223             format.setMaximumFractionDigits(_maxFractionDigits);
224         }
225         if (_maxIntegerDigitsSet)
226         {
227             format.setMaximumIntegerDigits(_maxIntegerDigits);
228         }
229         if (_minFractionDigitsSet)
230         {
231             format.setMinimumFractionDigits(_minFractionDigits);
232         }
233         if (_minIntegerDigitsSet)
234         {
235             format.setMinimumIntegerDigits(_minIntegerDigits);
236         }
237         formatCurrency(format);
238         try
239         {
240             return format.format(value);
241         }
242         catch (Exception e)
243         {
244             throw new ConverterException(_MessageUtils.getErrorMessage(facesContext, STRING_ID,
245                     new Object[]{value,_MessageUtils.getLabel(facesContext, uiComponent)}),e);
246         }
247     }
248 
249     private NumberFormat getNumberFormat(FacesContext facesContext)
250     {
251         Locale locale = _locale != null ? _locale : facesContext.getViewRoot().getLocale();
252 
253         if (_pattern == null && _type == null)
254         {
255             throw new ConverterException("Cannot get NumberFormat, either type or pattern needed.");
256         }
257 
258         // pattern
259         if (_pattern != null)
260         {
261             return new DecimalFormat(_pattern, new DecimalFormatSymbols(locale));
262         }
263 
264         // type
265         if (_type.equals("number"))
266         {
267             return NumberFormat.getNumberInstance(locale);
268         }
269         else if (_type.equals("currency"))
270         {
271             return NumberFormat.getCurrencyInstance(locale);
272         }
273         else if (_type.equals("percent"))
274         {
275             return NumberFormat.getPercentInstance(locale);
276         }
277         throw new ConverterException("Cannot get NumberFormat, illegal type " + _type);
278     }
279 
280     private void formatCurrency(NumberFormat format)
281     {
282         if (_currencyCode == null && _currencySymbol == null)
283         {
284             return;
285         }
286 
287         boolean useCurrencyCode;
288         if (JAVA_VERSION_14)
289         {
290             useCurrencyCode = _currencyCode != null;
291         }
292         else
293         {
294             useCurrencyCode = _currencySymbol == null;
295         }
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 static boolean checkJavaVersion14()
579     {
580         String version = System.getProperty("java.version");
581         if (version == null)
582         {
583             return false;
584         }
585         byte java14 = 0;
586         for (int idx = version.indexOf('.'), i = 0; idx > 0 || version != null; i++)
587         {
588             if (idx > 0)
589             {
590                 byte value = Byte.parseByte(version.substring(0, 1));
591                 version = version.substring(idx + 1, version.length());
592                 idx = version.indexOf('.');
593                 switch (i)
594                 {
595                     case 0:
596                         if (value == 1)
597                         {
598                             java14 = 1;
599                             break;
600                         }
601                         else if (value > 1)
602                         {
603                             java14 = 2;
604                         }
605                         // fallthru
606                     case 1:
607                         if (java14 > 0 && value >= 4)
608                         {
609                             java14 = 2;
610                         }
611                         //;
612                         // fallthru
613                     default:
614                         idx = 0;
615                         version = null;
616                         break;
617                 }
618             }
619             else
620             {
621                 byte value = Byte.parseByte(version.substring(0, 1));
622                 if (java14 > 0 && value >= 4)
623                 {
624                     java14 = 2;
625                 }
626                 break;
627             }
628         }
629         return java14 == 2;
630     }
631 
632 
633     private DecimalFormatSymbols getDecimalFormatSymbols()
634     {
635         return new DecimalFormatSymbols(getLocale());
636     }
637     
638     private boolean _initialStateMarked = false;
639 
640     public void clearInitialState()
641     {
642         _initialStateMarked = false;
643     }
644 
645     public boolean initialStateMarked()
646     {
647         return _initialStateMarked;
648     }
649 
650     public void markInitialState()
651     {
652         _initialStateMarked = true;
653     }
654 }