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