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