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