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