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 javax.faces.component.UIComponent;
22  import javax.faces.component.StateHolder;
23  import javax.faces.context.FacesContext;
24  import java.text.DecimalFormat;
25  import java.text.DecimalFormatSymbols;
26  import java.text.NumberFormat;
27  import java.text.ParseException;
28  import java.util.Currency;
29  import java.util.Locale;
30  
31  /**
32   * This tag creates a number formatting converter and associates it
33   * with the nearest parent UIComponent.
34   * 
35   * Unless otherwise specified, all attributes accept static values or EL expressions.
36   * 
37   * see Javadoc of <a href="http://java.sun.com/j2ee/javaserverfaces/1.1_01/docs/api/index.html">JSF Specification</a>
38   *
39   * @JSFConverter
40   *   name="f:convertNumber"
41   *   bodyContent="empty"
42   *   tagClass="org.apache.myfaces.taglib.core.ConvertNumberTag"
43   *
44   * @author Thomas Spiegl (latest modification by $Author: lu4242 $)
45   * @version $Revision: 1033980 $ $Date: 2010-11-11 11:15:43 -0500 (Thu, 11 Nov 2010) $
46   */
47  public class NumberConverter
48          implements Converter, StateHolder
49  {
50      // API FIELDS
51      public static final String CONVERTER_ID = "javax.faces.Number";
52  
53      // internal constants
54      private static final String CONVERSION_MESSAGE_ID = "javax.faces.convert.NumberConverter.CONVERSION";
55  
56  
57      private static final boolean JAVA_VERSION_14;
58  
59      static
60      {
61          JAVA_VERSION_14 = checkJavaVersion14();
62      }
63  
64      private String _currencyCode;
65      private String _currencySymbol;
66      private Locale _locale;
67      private int _maxFractionDigits;
68      private int _maxIntegerDigits;
69      private int _minFractionDigits;
70      private int _minIntegerDigits;
71      private String _pattern;
72      private String _type = "number";
73      private boolean _groupingUsed = true;
74      private boolean _integerOnly = false;
75      private boolean _transient;
76  
77      private boolean _maxFractionDigitsSet;
78      private boolean _maxIntegerDigitsSet;
79      private boolean _minFractionDigitsSet;
80      private boolean _minIntegerDigitsSet;
81  
82  
83      // CONSTRUCTORS
84      public NumberConverter()
85      {
86      }
87  
88      // METHODS
89      public Object getAsObject(FacesContext facesContext, UIComponent uiComponent, String value)
90      {
91          if (facesContext == null) throw new NullPointerException("facesContext");
92          if (uiComponent == null) throw new NullPointerException("uiComponent");
93  
94          if (value != null)
95          {
96              value = value.trim();
97              if (value.length() > 0)
98              {
99                  NumberFormat format = getNumberFormat(facesContext);
100                 format.setParseIntegerOnly(_integerOnly);
101                 
102                 DecimalFormat df = (DecimalFormat)format;
103                 //df.setParseBigDecimal(true);
104                 DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
105                 boolean changed = false;
106                 if(dfs.getGroupingSeparator() == '\u00a0')
107                 {
108                   dfs.setGroupingSeparator(' ');
109                   df.setDecimalFormatSymbols(dfs);
110                   changed = true;
111                 }
112                 
113                 formatCurrency(format);
114                 
115                 try
116                 {
117                     return format.parse(value);
118                 }
119                 catch (ParseException e)
120                 {
121                   if(changed)
122                   {
123                     dfs.setGroupingSeparator('\u00a0');
124                     df.setDecimalFormatSymbols(dfs);
125                   }
126                   try
127                   {
128                     return format.parse(value);
129                   }
130                   catch (ParseException pe)
131                   {
132 
133                     throw new ConverterException(_MessageUtils.getErrorMessage(facesContext,
134                                                                                CONVERSION_MESSAGE_ID,
135                                                                                new Object[]{uiComponent.getId(),value}), e);
136 
137                   }
138                 }
139             }
140         }
141         return null;
142     }
143 
144     public String getAsString(FacesContext facesContext, UIComponent uiComponent, Object value)
145     {
146         if (facesContext == null) throw new NullPointerException("facesContext");
147         if (uiComponent == null) throw new NullPointerException("uiComponent");
148 
149         if (value == null)
150         {
151             return "";
152         }
153         if (value instanceof String)
154         {
155             return (String)value;
156         }
157 
158         NumberFormat format = getNumberFormat(facesContext);
159         format.setGroupingUsed(_groupingUsed);
160         if (_maxFractionDigitsSet) format.setMaximumFractionDigits(_maxFractionDigits);
161         if (_maxIntegerDigitsSet) format.setMaximumIntegerDigits(_maxIntegerDigits);
162         if (_minFractionDigitsSet) format.setMinimumFractionDigits(_minFractionDigits);
163         if (_minIntegerDigitsSet) format.setMinimumIntegerDigits(_minIntegerDigits);
164         formatCurrency(format);
165         try
166         {
167             return format.format(value);
168         }
169         catch (Exception e)
170         {
171             throw new ConverterException("Cannot convert value '" + value + "'");
172         }
173     }
174 
175     private NumberFormat getNumberFormat(FacesContext facesContext)
176     {
177         Locale locale = _locale != null ? _locale : facesContext.getViewRoot().getLocale();
178 
179         if (_pattern == null && _type == null)
180         {
181             throw new ConverterException("Cannot get NumberFormat, either type or pattern needed.");
182         }
183 
184         // pattern
185         if (_pattern != null)
186         {
187             return new DecimalFormat(_pattern, new DecimalFormatSymbols(locale));
188         }
189 
190         // type
191         if (_type.equals("number"))
192         {
193             return NumberFormat.getNumberInstance(locale);
194         }
195         else if (_type.equals("currency"))
196         {
197             return NumberFormat.getCurrencyInstance(locale);
198         }
199         else if (_type.equals("percent"))
200         {
201             return NumberFormat.getPercentInstance(locale);
202         }
203         throw new ConverterException("Cannot get NumberFormat, illegal type " + _type);
204     }
205 
206     private void formatCurrency(NumberFormat format)
207     {
208         if (_currencyCode == null && _currencySymbol == null)
209         {
210             return;
211         }
212 
213         boolean useCurrencyCode;
214         if (JAVA_VERSION_14)
215         {
216             useCurrencyCode = _currencyCode != null;
217         }
218         else
219         {
220             useCurrencyCode = _currencySymbol == null;
221         }
222 
223         if (useCurrencyCode)
224         {
225             // set Currency
226             try
227             {
228                 format.setCurrency(Currency.getInstance(_currencyCode));
229             }
230             catch (Exception e)
231             {
232                 throw new ConverterException("Unable to get Currency instance for currencyCode " +
233                                              _currencyCode);
234             }
235         }
236         else if (format instanceof DecimalFormat)
237 
238         {
239             DecimalFormat dFormat = (DecimalFormat)format;
240             DecimalFormatSymbols symbols = dFormat.getDecimalFormatSymbols();
241             symbols.setCurrencySymbol(_currencySymbol);
242             dFormat.setDecimalFormatSymbols(symbols);
243         }
244     }
245 
246     // STATE SAVE/RESTORE
247     public void restoreState(FacesContext facesContext, Object state)
248     {
249         Object values[] = (Object[])state;
250         _currencyCode = (String)values[0];
251         _currencySymbol = (String)values[1];
252         _locale = (Locale)values[2];
253         Integer value = (Integer)values[3];
254         _maxFractionDigits = value != null ? value.intValue() : 0;
255         value = (Integer)values[4];
256         _maxIntegerDigits = value != null ? value.intValue() : 0;
257         value = (Integer)values[5];
258         _minFractionDigits = value != null ? value.intValue() : 0;
259         value = (Integer)values[6];
260         _minIntegerDigits = value != null ? value.intValue() : 0;
261         _pattern = (String)values[7];
262         _type = (String)values[8];
263         _groupingUsed = ((Boolean)values[9]).booleanValue();
264         _integerOnly = ((Boolean)values[10]).booleanValue();
265         _maxFractionDigitsSet = ((Boolean)values[11]).booleanValue();
266         _maxIntegerDigitsSet = ((Boolean)values[12]).booleanValue();
267         _minFractionDigitsSet = ((Boolean)values[13]).booleanValue();
268         _minIntegerDigitsSet = ((Boolean)values[14]).booleanValue();
269     }
270 
271     public Object saveState(FacesContext facesContext)
272     {
273         Object values[] = new Object[15];
274         values[0] = _currencyCode;
275         values[1] = _currencySymbol;
276         values[2] = _locale;
277         values[3] = _maxFractionDigitsSet ? new Integer(_maxFractionDigits) : null;
278         values[4] = _maxIntegerDigitsSet ? new Integer(_maxIntegerDigits) : null;
279         values[5] = _minFractionDigitsSet ? new Integer(_minFractionDigits) : null;
280         values[6] = _minIntegerDigitsSet ? new Integer(_minIntegerDigits) : null;
281         values[7] = _pattern;
282         values[8] = _type;
283         values[9] = _groupingUsed ? Boolean.TRUE : Boolean.FALSE;
284         values[10] = _integerOnly ? Boolean.TRUE : Boolean.FALSE;
285         values[11] = _maxFractionDigitsSet ? Boolean.TRUE : Boolean.FALSE;
286         values[12] = _maxIntegerDigitsSet ? Boolean.TRUE : Boolean.FALSE;
287         values[13] = _minFractionDigitsSet ? Boolean.TRUE : Boolean.FALSE;
288         values[14] = _minIntegerDigitsSet ? Boolean.TRUE : Boolean.FALSE;
289         return values;
290     }
291 
292     // GETTER & SETTER
293     
294     /**
295      * ISO 4217 currency code
296      * 
297      * @JSFProperty
298      */    
299     public String getCurrencyCode()
300     {
301         return _currencyCode != null ?
302                _currencyCode :
303                getDecimalFormatSymbols().getInternationalCurrencySymbol();
304     }
305 
306     public void setCurrencyCode(String currencyCode)
307     {
308         _currencyCode = currencyCode;
309     }
310 
311     /**
312      * The currency symbol used to format a currency value.  Defaults
313      * to the currency symbol for locale.
314      * 
315      * @JSFProperty
316      */
317     public String getCurrencySymbol()
318     {
319         return _currencySymbol != null ?
320                _currencySymbol :
321                getDecimalFormatSymbols().getCurrencySymbol();
322     }
323 
324     public void setCurrencySymbol(String currencySymbol)
325     {
326         _currencySymbol = currencySymbol;
327     }
328 
329     /**
330      * Specifies whether output will contain grouping separators.  Default: true.
331      * 
332      * @JSFProperty
333      */
334     public boolean isGroupingUsed()
335     {
336         return _groupingUsed;
337     }
338 
339     public void setGroupingUsed(boolean groupingUsed)
340     {
341         _groupingUsed = groupingUsed;
342     }
343 
344     /**
345      * Specifies whether only the integer part of the input will be parsed.  Default: false.
346      * 
347      * @JSFProperty
348      */
349     public boolean isIntegerOnly()
350     {
351         return _integerOnly;
352     }
353 
354     public void setIntegerOnly(boolean integerOnly)
355     {
356         _integerOnly = integerOnly;
357     }
358 
359     /**
360      * The name of the locale to be used, instead of the default as
361      * specified in the faces configuration file.
362      * 
363      * @JSFProperty
364      */
365     public Locale getLocale()
366     {
367         if (_locale != null) return _locale;
368         FacesContext context = FacesContext.getCurrentInstance();
369         return context.getViewRoot().getLocale();
370     }
371 
372     public void setLocale(Locale locale)
373     {
374         _locale = locale;
375     }
376 
377     /**
378      * The maximum number of digits in the fractional portion of the number.
379      * 
380      * @JSFProperty
381      */
382     public int getMaxFractionDigits()
383     {
384         return _maxFractionDigits;
385     }
386 
387     public void setMaxFractionDigits(int maxFractionDigits)
388     {
389         _maxFractionDigitsSet = true;
390         _maxFractionDigits = maxFractionDigits;
391     }
392 
393     /**
394      * The maximum number of digits in the integer portion of the number.
395      * 
396      * @JSFProperty
397      */
398     public int getMaxIntegerDigits()
399     {
400         return _maxIntegerDigits;
401     }
402 
403     public void setMaxIntegerDigits(int maxIntegerDigits)
404     {
405         _maxIntegerDigitsSet = true;
406         _maxIntegerDigits = maxIntegerDigits;
407     }
408 
409     /**
410      * The minimum number of digits in the fractional portion of the number.
411      * 
412      * @JSFProperty
413      */
414     public int getMinFractionDigits()
415     {
416         return _minFractionDigits;
417     }
418 
419     public void setMinFractionDigits(int minFractionDigits)
420     {
421         _minFractionDigitsSet = true;
422         _minFractionDigits = minFractionDigits;
423     }
424 
425     /**
426      * The minimum number of digits in the integer portion of the number.
427      * 
428      * @JSFProperty
429      */
430     public int getMinIntegerDigits()
431     {
432         return _minIntegerDigits;
433     }
434 
435     public void setMinIntegerDigits(int minIntegerDigits)
436     {
437         _minIntegerDigitsSet = true;
438         _minIntegerDigits = minIntegerDigits;
439     }
440 
441     /**
442      * A custom Date formatting pattern, in the format used by java.text.SimpleDateFormat.
443      * 
444      * @JSFProperty
445      */
446     public String getPattern()
447     {
448         return _pattern;
449     }
450 
451     public void setPattern(String pattern)
452     {
453         _pattern = pattern;
454     }
455 
456     public boolean isTransient()
457     {
458         return _transient;
459     }
460 
461     public void setTransient(boolean aTransient)
462     {
463         _transient = aTransient;
464     }
465 
466     /**
467      * The type of formatting/parsing to be performed.  Values include:
468      * number, currency, and percent.  Default: number.
469      * 
470      * @JSFProperty
471      */
472     public String getType()
473     {
474         return _type;
475     }
476 
477     public void setType(String type)
478     {
479         //TODO: validate type
480         _type = type;
481     }
482 
483     private static boolean checkJavaVersion14()
484     {
485         String version = System.getProperty("java.version");
486         if (version == null)
487         {
488             return false;
489         }
490         byte java14 = 0;
491         for (int idx = version.indexOf('.'), i = 0; idx > 0 || version != null; i++)
492         {
493             if (idx > 0)
494             {
495                 byte value = Byte.parseByte(version.substring(0, 1));
496                 version = version.substring(idx + 1, version.length());
497                 idx = version.indexOf('.');
498                 switch (i)
499                 {
500                     case 0:
501                         if (value == 1)
502                         {
503                             java14 = 1;
504                             break;
505                         }
506                         else if (value > 1)
507                         {
508                             java14 = 2;
509                         }
510                     case 1:
511                         if (java14 > 0 && value >= 4)
512                         {
513                             java14 = 2;
514                         }
515                         ;
516                     default:
517                         idx = 0;
518                         version = null;
519                         break;
520                 }
521             }
522             else
523             {
524                 byte value = Byte.parseByte(version.substring(0, 1));
525                 if (java14 > 0 && value >= 4)
526                 {
527                     java14 = 2;
528                 }
529                 break;
530             }
531         }
532         return java14 == 2;
533     }
534 
535 
536     private DecimalFormatSymbols getDecimalFormatSymbols()
537     {
538         return new DecimalFormatSymbols(getLocale());
539     }
540 }