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.StateHolder;
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: 1034000 $ $Date: 2010-11-11 12:04:25 -0500 (Thu, 11 Nov 2010) $
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, StateHolder
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         Object values[] = (Object[])state;
288         _currencyCode = (String)values[0];
289         _currencySymbol = (String)values[1];
290         _locale = (Locale)values[2];
291         Integer value = (Integer)values[3];
292         _maxFractionDigits = value != null ? value.intValue() : 0;
293         value = (Integer)values[4];
294         _maxIntegerDigits = value != null ? value.intValue() : 0;
295         value = (Integer)values[5];
296         _minFractionDigits = value != null ? value.intValue() : 0;
297         value = (Integer)values[6];
298         _minIntegerDigits = value != null ? value.intValue() : 0;
299         _pattern = (String)values[7];
300         _type = (String)values[8];
301         _groupingUsed = ((Boolean)values[9]).booleanValue();
302         _integerOnly = ((Boolean)values[10]).booleanValue();
303         _maxFractionDigitsSet = ((Boolean)values[11]).booleanValue();
304         _maxIntegerDigitsSet = ((Boolean)values[12]).booleanValue();
305         _minFractionDigitsSet = ((Boolean)values[13]).booleanValue();
306         _minIntegerDigitsSet = ((Boolean)values[14]).booleanValue();
307     }
308 
309     public Object saveState(FacesContext facesContext)
310     {
311         Object values[] = new Object[15];
312         values[0] = _currencyCode;
313         values[1] = _currencySymbol;
314         values[2] = _locale;
315         values[3] = _maxFractionDigitsSet ? new Integer(_maxFractionDigits) : null;
316         values[4] = _maxIntegerDigitsSet ? new Integer(_maxIntegerDigits) : null;
317         values[5] = _minFractionDigitsSet ? new Integer(_minFractionDigits) : null;
318         values[6] = _minIntegerDigitsSet ? new Integer(_minIntegerDigits) : null;
319         values[7] = _pattern;
320         values[8] = _type;
321         values[9] = _groupingUsed ? Boolean.TRUE : Boolean.FALSE;
322         values[10] = _integerOnly ? Boolean.TRUE : Boolean.FALSE;
323         values[11] = _maxFractionDigitsSet ? Boolean.TRUE : Boolean.FALSE;
324         values[12] = _maxIntegerDigitsSet ? Boolean.TRUE : Boolean.FALSE;
325         values[13] = _minFractionDigitsSet ? Boolean.TRUE : Boolean.FALSE;
326         values[14] = _minIntegerDigitsSet ? Boolean.TRUE : Boolean.FALSE;
327         return values;
328     }
329 
330     // GETTER & SETTER
331     
332     /**
333      * ISO 4217 currency code
334      * 
335      */
336     @JSFProperty
337     public String getCurrencyCode()
338     {
339         return _currencyCode != null ?
340                _currencyCode :
341                getDecimalFormatSymbols().getInternationalCurrencySymbol();
342     }
343 
344     public void setCurrencyCode(String currencyCode)
345     {
346         _currencyCode = currencyCode;
347     }
348 
349     /**
350      * The currency symbol used to format a currency value.  Defaults
351      * to the currency symbol for locale.
352      * 
353      */
354     @JSFProperty
355     public String getCurrencySymbol()
356     {
357         return _currencySymbol != null ?
358                _currencySymbol :
359                getDecimalFormatSymbols().getCurrencySymbol();
360     }
361 
362     public void setCurrencySymbol(String currencySymbol)
363     {
364         _currencySymbol = currencySymbol;
365     }
366 
367     /**
368      * Specifies whether output will contain grouping separators.  Default: true.
369      * 
370      */
371     @JSFProperty
372     public boolean isGroupingUsed()
373     {
374         return _groupingUsed;
375     }
376 
377     public void setGroupingUsed(boolean groupingUsed)
378     {
379         _groupingUsed = groupingUsed;
380     }
381 
382     /**
383      * Specifies whether only the integer part of the input will be parsed.  Default: false.
384      * 
385      */
386     @JSFProperty
387     public boolean isIntegerOnly()
388     {
389         return _integerOnly;
390     }
391 
392     public void setIntegerOnly(boolean integerOnly)
393     {
394         _integerOnly = integerOnly;
395     }
396 
397     /**
398      * The name of the locale to be used, instead of the default as
399      * specified in the faces configuration file.
400      * 
401      */
402     @JSFProperty
403     public Locale getLocale()
404     {
405         if (_locale != null) return _locale;
406         FacesContext context = FacesContext.getCurrentInstance();
407         return context.getViewRoot().getLocale();
408     }
409 
410     public void setLocale(Locale locale)
411     {
412         _locale = locale;
413     }
414 
415     /**
416      * The maximum number of digits in the fractional portion of the number.
417      * 
418      */
419     @JSFProperty
420     public int getMaxFractionDigits()
421     {
422         return _maxFractionDigits;
423     }
424 
425     public void setMaxFractionDigits(int maxFractionDigits)
426     {
427         _maxFractionDigitsSet = true;
428         _maxFractionDigits = maxFractionDigits;
429     }
430 
431     /**
432      * The maximum number of digits in the integer portion of the number.
433      * 
434      */
435     @JSFProperty
436     public int getMaxIntegerDigits()
437     {
438         return _maxIntegerDigits;
439     }
440 
441     public void setMaxIntegerDigits(int maxIntegerDigits)
442     {
443         _maxIntegerDigitsSet = true;
444         _maxIntegerDigits = maxIntegerDigits;
445     }
446 
447     /**
448      * The minimum number of digits in the fractional portion of the number.
449      * 
450      */
451     @JSFProperty
452     public int getMinFractionDigits()
453     {
454         return _minFractionDigits;
455     }
456 
457     public void setMinFractionDigits(int minFractionDigits)
458     {
459         _minFractionDigitsSet = true;
460         _minFractionDigits = minFractionDigits;
461     }
462 
463     /**
464      * The minimum number of digits in the integer portion of the number.
465      * 
466      */
467     @JSFProperty
468     public int getMinIntegerDigits()
469     {
470         return _minIntegerDigits;
471     }
472 
473     public void setMinIntegerDigits(int minIntegerDigits)
474     {
475         _minIntegerDigitsSet = true;
476         _minIntegerDigits = minIntegerDigits;
477     }
478 
479     /**
480      * A custom Date formatting pattern, in the format used by java.text.SimpleDateFormat.
481      * 
482      */
483     @JSFProperty
484     public String getPattern()
485     {
486         return _pattern;
487     }
488 
489     public void setPattern(String pattern)
490     {
491         _pattern = pattern;
492     }
493 
494     public boolean isTransient()
495     {
496         return _transient;
497     }
498 
499     public void setTransient(boolean aTransient)
500     {
501         _transient = aTransient;
502     }
503 
504     /**
505      * The type of formatting/parsing to be performed.  Values include:
506      * number, currency, and percent.  Default: number.
507      * 
508      */
509     @JSFProperty
510     public String getType()
511     {
512         return _type;
513     }
514 
515     public void setType(String type)
516     {
517         //TODO: validate type
518         _type = type;
519     }
520 
521     private static boolean checkJavaVersion14()
522     {
523         String version = System.getProperty("java.version");
524         if (version == null)
525         {
526             return false;
527         }
528         byte java14 = 0;
529         for (int idx = version.indexOf('.'), i = 0; idx > 0 || version != null; i++)
530         {
531             if (idx > 0)
532             {
533                 byte value = Byte.parseByte(version.substring(0, 1));
534                 version = version.substring(idx + 1, version.length());
535                 idx = version.indexOf('.');
536                 switch (i)
537                 {
538                     case 0:
539                         if (value == 1)
540                         {
541                             java14 = 1;
542                             break;
543                         }
544                         else if (value > 1)
545                         {
546                             java14 = 2;
547                         }
548                     case 1:
549                         if (java14 > 0 && value >= 4)
550                         {
551                             java14 = 2;
552                         }
553                         ;
554                     default:
555                         idx = 0;
556                         version = null;
557                         break;
558                 }
559             }
560             else
561             {
562                 byte value = Byte.parseByte(version.substring(0, 1));
563                 if (java14 > 0 && value >= 4)
564                 {
565                     java14 = 2;
566                 }
567                 break;
568             }
569         }
570         return java14 == 2;
571     }
572 
573 
574     private DecimalFormatSymbols getDecimalFormatSymbols()
575     {
576         return new DecimalFormatSymbols(getLocale());
577     }
578 }