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