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