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