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