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