Clover coverage report - Code Coverage for tapestry release 3.1-alpha-1
Coverage timestamp: Mon Feb 21 2005 09:16:14 EST
file stats: LOC: 663   Methods: 54
NCLOC: 407   Classes: 12
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover
 
 Source file Conditionals Statements Methods TOTAL
NumberValidator.java 52.9% 78.7% 70.4% 73.1%
coverage coverage
 1   
 // Copyright 2004, 2005 The Apache Software Foundation
 2   
 //
 3   
 // Licensed under the Apache License, Version 2.0 (the "License");
 4   
 // you may not use this file except in compliance with the License.
 5   
 // You may obtain a copy of the License at
 6   
 //
 7   
 //     http://www.apache.org/licenses/LICENSE-2.0
 8   
 //
 9   
 // Unless required by applicable law or agreed to in writing, software
 10   
 // distributed under the License is distributed on an "AS IS" BASIS,
 11   
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12   
 // See the License for the specific language governing permissions and
 13   
 // limitations under the License.
 14   
 
 15   
 package org.apache.tapestry.valid;
 16   
 
 17   
 import java.math.BigDecimal;
 18   
 import java.math.BigInteger;
 19   
 import java.util.HashMap;
 20   
 import java.util.Map;
 21   
 
 22   
 import org.apache.hivemind.ApplicationRuntimeException;
 23   
 import org.apache.hivemind.lib.util.AdapterRegistry;
 24   
 import org.apache.hivemind.lib.util.AdapterRegistryImpl;
 25   
 import org.apache.tapestry.IMarkupWriter;
 26   
 import org.apache.tapestry.IRequestCycle;
 27   
 import org.apache.tapestry.Tapestry;
 28   
 import org.apache.tapestry.form.IFormComponent;
 29   
 
 30   
 /**
 31   
  * Simple validation for standard number classes. This is probably insufficient for anything tricky
 32   
  * and application specific, such as parsing currency.
 33   
  * 
 34   
  * @author Howard Lewis Ship
 35   
  * @since 1.0.8
 36   
  */
 37   
 
 38   
 public class NumberValidator extends BaseValidator
 39   
 {
 40   
     private static final Map TYPES = new HashMap();
 41   
 
 42   
     static
 43   
     {
 44  1
         TYPES.put("boolean", boolean.class);
 45  1
         TYPES.put("Boolean", Boolean.class);
 46  1
         TYPES.put("java.lang.Boolean", Boolean.class);
 47  1
         TYPES.put("char", char.class);
 48  1
         TYPES.put("Character", Character.class);
 49  1
         TYPES.put("java.lang.Character", Character.class);
 50  1
         TYPES.put("short", short.class);
 51  1
         TYPES.put("Short", Short.class);
 52  1
         TYPES.put("java.lang.Short", Short.class);
 53  1
         TYPES.put("int", int.class);
 54  1
         TYPES.put("Integer", Integer.class);
 55  1
         TYPES.put("java.lang.Integer", Integer.class);
 56  1
         TYPES.put("long", long.class);
 57  1
         TYPES.put("Long", Long.class);
 58  1
         TYPES.put("java.lang.Long", Long.class);
 59  1
         TYPES.put("float", float.class);
 60  1
         TYPES.put("Float", Float.class);
 61  1
         TYPES.put("java.lang.Float", Float.class);
 62  1
         TYPES.put("byte", byte.class);
 63  1
         TYPES.put("Byte", Byte.class);
 64  1
         TYPES.put("java.lang.Byte", Byte.class);
 65  1
         TYPES.put("double", double.class);
 66  1
         TYPES.put("Double", Double.class);
 67  1
         TYPES.put("java.lang.Double", Double.class);
 68  1
         TYPES.put("java.math.BigInteger", BigInteger.class);
 69  1
         TYPES.put("java.math.BigDecimal", BigDecimal.class);
 70   
     }
 71   
 
 72   
     private Class _valueTypeClass = int.class;
 73   
 
 74   
     private boolean _zeroIsNull;
 75   
 
 76   
     private Number _minimum;
 77   
 
 78   
     private Number _maximum;
 79   
 
 80   
     private String _scriptPath = "/org/apache/tapestry/valid/NumberValidator.script";
 81   
 
 82   
     private String _invalidNumericFormatMessage;
 83   
 
 84   
     private String _invalidIntegerFormatMessage;
 85   
 
 86   
     private String _numberTooSmallMessage;
 87   
 
 88   
     private String _numberTooLargeMessage;
 89   
 
 90   
     private String _numberRangeMessage;
 91   
 
 92   
     private static AdapterRegistry _numberAdaptors = new AdapterRegistryImpl();
 93   
 
 94   
     public final static int NUMBER_TYPE_INTEGER = 0;
 95   
 
 96   
     public final static int NUMBER_TYPE_REAL = 1;
 97   
 
 98   
     /**
 99   
      * This class is not meant for use outside of NumberValidator; it is public only to fascilitate
 100   
      * some unit testing.
 101   
      */
 102   
     public static abstract class NumberAdaptor
 103   
     {
 104   
         /**
 105   
          * Parses a non-empty {@link String}into the correct subclass of {@link Number}.
 106   
          * 
 107   
          * @throws NumberFormatException
 108   
          *             if the String can not be parsed.
 109   
          */
 110   
 
 111   
         abstract public Number parse(String value);
 112   
 
 113   
         /**
 114   
          * Indicates the type of the number represented -- integer or real. The information is used
 115   
          * to build the client-side validator. This method could return a boolean, but returns an
 116   
          * int to allow future extensions of the validator.
 117   
          * 
 118   
          * @return one of the predefined number types
 119   
          */
 120   
         abstract public int getNumberType();
 121   
 
 122  15
         public int compare(Number left, Number right)
 123   
         {
 124  15
             if (!left.getClass().equals(right.getClass()))
 125  8
                 right = coerce(right);
 126   
 
 127  15
             Comparable lc = (Comparable) left;
 128   
 
 129  15
             return lc.compareTo(right);
 130   
         }
 131   
 
 132   
         /**
 133   
          * Invoked when comparing two Numbers of different types. The number is cooerced from its
 134   
          * ordinary type to the correct type for comparison.
 135   
          * 
 136   
          * @since 3.0
 137   
          */
 138   
         protected abstract Number coerce(Number number);
 139   
     }
 140   
 
 141   
     private static abstract class IntegerNumberAdaptor extends NumberAdaptor
 142   
     {
 143  6
         public int getNumberType()
 144   
         {
 145  6
             return NUMBER_TYPE_INTEGER;
 146   
         }
 147   
     }
 148   
 
 149   
     private static abstract class RealNumberAdaptor extends NumberAdaptor
 150   
     {
 151  3
         public int getNumberType()
 152   
         {
 153  3
             return NUMBER_TYPE_REAL;
 154   
         }
 155   
     }
 156   
 
 157   
     private static class ByteAdaptor extends IntegerNumberAdaptor
 158   
     {
 159  1
         public Number parse(String value)
 160   
         {
 161  1
             return new Byte(value);
 162   
         }
 163   
 
 164  1
         protected Number coerce(Number number)
 165   
         {
 166  1
             return new Byte(number.byteValue());
 167   
         }
 168   
     }
 169   
 
 170   
     private static class ShortAdaptor extends IntegerNumberAdaptor
 171   
     {
 172  1
         public Number parse(String value)
 173   
         {
 174  1
             return new Short(value);
 175   
         }
 176   
 
 177  1
         protected Number coerce(Number number)
 178   
         {
 179  1
             return new Short(number.shortValue());
 180   
         }
 181   
     }
 182   
 
 183   
     private static class IntAdaptor extends IntegerNumberAdaptor
 184   
     {
 185  9
         public Number parse(String value)
 186   
         {
 187  9
             return new Integer(value);
 188   
         }
 189   
 
 190  1
         protected Number coerce(Number number)
 191   
         {
 192  1
             return new Integer(number.intValue());
 193   
         }
 194   
     }
 195   
 
 196   
     private static class LongAdaptor extends IntegerNumberAdaptor
 197   
     {
 198  1
         public Number parse(String value)
 199   
         {
 200  1
             return new Long(value);
 201   
         }
 202   
 
 203  1
         protected Number coerce(Number number)
 204   
         {
 205  1
             return new Long(number.longValue());
 206   
         }
 207   
     }
 208   
 
 209   
     private static class FloatAdaptor extends RealNumberAdaptor
 210   
     {
 211  1
         public Number parse(String value)
 212   
         {
 213  1
             return new Float(value);
 214   
         }
 215   
 
 216  1
         protected Number coerce(Number number)
 217   
         {
 218  1
             return new Float(number.floatValue());
 219   
         }
 220   
     }
 221   
 
 222   
     private static class DoubleAdaptor extends RealNumberAdaptor
 223   
     {
 224  1
         public Number parse(String value)
 225   
         {
 226  1
             return new Double(value);
 227   
         }
 228   
 
 229  1
         protected Number coerce(Number number)
 230   
         {
 231  1
             return new Double(number.doubleValue());
 232   
         }
 233   
     }
 234   
 
 235   
     private static class BigDecimalAdaptor extends RealNumberAdaptor
 236   
     {
 237  1
         public Number parse(String value)
 238   
         {
 239  1
             return new BigDecimal(value);
 240   
         }
 241   
 
 242  1
         protected Number coerce(Number number)
 243   
         {
 244  1
             return new BigDecimal(number.doubleValue());
 245   
         }
 246   
     }
 247   
 
 248   
     private static class BigIntegerAdaptor extends IntegerNumberAdaptor
 249   
     {
 250  1
         public Number parse(String value)
 251   
         {
 252  1
             return new BigInteger(value);
 253   
         }
 254   
 
 255  1
         protected Number coerce(Number number)
 256   
         {
 257  1
             return new BigInteger(number.toString());
 258   
         }
 259   
     }
 260   
 
 261   
     static
 262   
     {
 263  1
         NumberAdaptor byteAdaptor = new ByteAdaptor();
 264  1
         NumberAdaptor shortAdaptor = new ShortAdaptor();
 265  1
         NumberAdaptor intAdaptor = new IntAdaptor();
 266  1
         NumberAdaptor longAdaptor = new LongAdaptor();
 267  1
         NumberAdaptor floatAdaptor = new FloatAdaptor();
 268  1
         NumberAdaptor doubleAdaptor = new DoubleAdaptor();
 269   
 
 270  1
         _numberAdaptors.register(Byte.class, byteAdaptor);
 271  1
         _numberAdaptors.register(byte.class, byteAdaptor);
 272  1
         _numberAdaptors.register(Short.class, shortAdaptor);
 273  1
         _numberAdaptors.register(short.class, shortAdaptor);
 274  1
         _numberAdaptors.register(Integer.class, intAdaptor);
 275  1
         _numberAdaptors.register(int.class, intAdaptor);
 276  1
         _numberAdaptors.register(Long.class, longAdaptor);
 277  1
         _numberAdaptors.register(long.class, longAdaptor);
 278  1
         _numberAdaptors.register(Float.class, floatAdaptor);
 279  1
         _numberAdaptors.register(float.class, floatAdaptor);
 280  1
         _numberAdaptors.register(Double.class, doubleAdaptor);
 281  1
         _numberAdaptors.register(double.class, doubleAdaptor);
 282   
 
 283  1
         _numberAdaptors.register(BigDecimal.class, new BigDecimalAdaptor());
 284  1
         _numberAdaptors.register(BigInteger.class, new BigIntegerAdaptor());
 285   
     }
 286   
 
 287  16
     public String toString(IFormComponent field, Object value)
 288   
     {
 289  16
         if (value == null)
 290  0
             return null;
 291   
 
 292  16
         if (_zeroIsNull)
 293   
         {
 294  0
             Number number = (Number) value;
 295   
 
 296  0
             if (number.doubleValue() == 0.0)
 297  0
                 return null;
 298   
         }
 299   
 
 300  16
         return value.toString();
 301   
     }
 302   
 
 303  16
     private NumberAdaptor getAdaptor(IFormComponent field)
 304   
     {
 305  16
         NumberAdaptor result = getAdaptor(_valueTypeClass);
 306   
 
 307  16
         if (result == null)
 308  0
             throw new ApplicationRuntimeException(Tapestry.format(
 309   
                     "NumberValidator.no-adaptor-for-field",
 310   
                     field,
 311   
                     _valueTypeClass.getName()));
 312   
 
 313  16
         return result;
 314   
     }
 315   
 
 316   
     /**
 317   
      * Returns an adaptor for the given type.
 318   
      * <p>
 319   
      * Note: this method exists only for testing purposes. It is not meant to be invoked by user
 320   
      * code and is subject to change at any time.
 321   
      * 
 322   
      * @param type
 323   
      *            the type (a Number subclass) for which to return an adaptor
 324   
      * @return the adaptor, or null if no such adaptor may be found
 325   
      * @since 3.0
 326   
      */
 327  32
     public static NumberAdaptor getAdaptor(Class type)
 328   
     {
 329  32
         return (NumberAdaptor) _numberAdaptors.getAdapter(type);
 330   
     }
 331   
 
 332  16
     public Object toObject(IFormComponent field, String value) throws ValidatorException
 333   
     {
 334  16
         if (checkRequired(field, value))
 335  0
             return null;
 336   
 
 337  16
         NumberAdaptor adaptor = getAdaptor(field);
 338  16
         Number result = null;
 339   
 
 340  16
         try
 341   
         {
 342  16
             result = adaptor.parse(value);
 343   
         }
 344   
         catch (NumberFormatException ex)
 345   
         {
 346  2
             throw new ValidatorException(buildInvalidNumericFormatMessage(field),
 347   
                     ValidationConstraint.NUMBER_FORMAT);
 348   
         }
 349   
 
 350  14
         if (_minimum != null && adaptor.compare(result, _minimum) < 0)
 351  2
             throw new ValidatorException(buildNumberTooSmallMessage(field),
 352   
                     ValidationConstraint.TOO_SMALL);
 353   
 
 354  12
         if (_maximum != null && adaptor.compare(result, _maximum) > 0)
 355  2
             throw new ValidatorException(buildNumberTooLargeMessage(field),
 356   
                     ValidationConstraint.TOO_LARGE);
 357   
 
 358  10
         return result;
 359   
     }
 360   
 
 361  0
     public Number getMaximum()
 362   
     {
 363  0
         return _maximum;
 364   
     }
 365   
 
 366  0
     public boolean getHasMaximum()
 367   
     {
 368  0
         return _maximum != null;
 369   
     }
 370   
 
 371  4
     public void setMaximum(Number maximum)
 372   
     {
 373  4
         _maximum = maximum;
 374   
     }
 375   
 
 376  0
     public Number getMinimum()
 377   
     {
 378  0
         return _minimum;
 379   
     }
 380   
 
 381  0
     public boolean getHasMinimum()
 382   
     {
 383  0
         return _minimum != null;
 384   
     }
 385   
 
 386  4
     public void setMinimum(Number minimum)
 387   
     {
 388  4
         _minimum = minimum;
 389   
     }
 390   
 
 391   
     /**
 392   
      * If true, then when rendering, a zero is treated as a non-value, and null is returned. If
 393   
      * false, the default, then zero is rendered as zero.
 394   
      */
 395   
 
 396  0
     public boolean getZeroIsNull()
 397   
     {
 398  0
         return _zeroIsNull;
 399   
     }
 400   
 
 401  0
     public void setZeroIsNull(boolean zeroIsNull)
 402   
     {
 403  0
         _zeroIsNull = zeroIsNull;
 404   
     }
 405   
 
 406   
     /**
 407   
      * @since 2.2
 408   
      */
 409   
 
 410  3
     public void renderValidatorContribution(IFormComponent field, IMarkupWriter writer,
 411   
             IRequestCycle cycle)
 412   
     {
 413  3
         if (!isClientScriptingEnabled())
 414  2
             return;
 415   
 
 416  1
         if (!(isRequired() || _minimum != null || _maximum != null))
 417  0
             return;
 418   
 
 419  1
         Map symbols = new HashMap();
 420   
 
 421  1
         if (isRequired())
 422  1
             symbols.put("requiredMessage", buildRequiredMessage(field));
 423   
 
 424  1
         if (isIntegerNumber())
 425  1
             symbols.put("formatMessage", buildInvalidIntegerFormatMessage(field));
 426   
         else
 427  0
             symbols.put("formatMessage", buildInvalidNumericFormatMessage(field));
 428   
 
 429  1
         if (_minimum != null || _maximum != null)
 430  0
             symbols.put("rangeMessage", buildRangeMessage(field));
 431   
 
 432  1
         processValidatorScript(_scriptPath, cycle, field, symbols);
 433   
     }
 434   
 
 435  0
     private String buildRangeMessage(IFormComponent field)
 436   
     {
 437  0
         if (_minimum != null && _maximum != null)
 438  0
             return buildNumberRangeMessage(field);
 439   
 
 440  0
         if (_maximum != null)
 441  0
             return buildNumberTooLargeMessage(field);
 442   
 
 443  0
         return buildNumberTooSmallMessage(field);
 444   
     }
 445   
 
 446   
     /**
 447   
      * @since 2.2
 448   
      */
 449   
 
 450  0
     public String getScriptPath()
 451   
     {
 452  0
         return _scriptPath;
 453   
     }
 454   
 
 455   
     /**
 456   
      * Allows a developer to use the existing validation logic with a different client-side script.
 457   
      * This is often sufficient to allow application-specific error presentation (perhaps by using
 458   
      * DHTML to update the content of a &lt;span&gt; tag, or to use a more sophisticated pop-up
 459   
      * window than <code>window.alert()</code>).
 460   
      * 
 461   
      * @since 2.2
 462   
      */
 463   
 
 464  0
     public void setScriptPath(String scriptPath)
 465   
     {
 466  0
         _scriptPath = scriptPath;
 467   
     }
 468   
 
 469   
     /**
 470   
      * Sets the value type from a string type name. The name may be a scalar numeric type, a fully
 471   
      * qualified class name, or the name of a numeric wrapper type from java.lang (with the package
 472   
      * name omitted).
 473   
      * 
 474   
      * @since 3.0
 475   
      */
 476   
 
 477  2
     public void setValueType(String typeName)
 478   
     {
 479  2
         Class typeClass = (Class) TYPES.get(typeName);
 480   
 
 481  2
         if (typeClass == null)
 482  0
             throw new ApplicationRuntimeException(Tapestry.format(
 483   
                     "NumberValidator.unknown-type",
 484   
                     typeName));
 485   
 
 486  2
         _valueTypeClass = typeClass;
 487   
     }
 488   
 
 489   
     /** @since 3.0 * */
 490   
 
 491  15
     public void setValueTypeClass(Class valueTypeClass)
 492   
     {
 493  15
         _valueTypeClass = valueTypeClass;
 494   
     }
 495   
 
 496   
     /**
 497   
      * Returns the value type to convert strings back into. The default is int.
 498   
      * 
 499   
      * @since 3.0
 500   
      */
 501   
 
 502  0
     public Class getValueTypeClass()
 503   
     {
 504  0
         return _valueTypeClass;
 505   
     }
 506   
 
 507   
     /** @since 3.0 */
 508   
 
 509  2
     public String getInvalidNumericFormatMessage()
 510   
     {
 511  2
         return _invalidNumericFormatMessage;
 512   
     }
 513   
 
 514   
     /** @since 3.0 */
 515   
 
 516  1
     public String getInvalidIntegerFormatMessage()
 517   
     {
 518  1
         return _invalidIntegerFormatMessage;
 519   
     }
 520   
 
 521   
     /** @since 3.0 */
 522   
 
 523  0
     public String getNumberRangeMessage()
 524   
     {
 525  0
         return _numberRangeMessage;
 526   
     }
 527   
 
 528   
     /** @since 3.0 */
 529   
 
 530  0
     public String getNumberTooLargeMessage()
 531   
     {
 532  0
         return _numberTooLargeMessage;
 533   
     }
 534   
 
 535   
     /** @since 3.0 */
 536   
 
 537  0
     public String getNumberTooSmallMessage()
 538   
     {
 539  0
         return _numberTooSmallMessage;
 540   
     }
 541   
 
 542   
     /**
 543   
      * Overrides the <code>invalid-numeric-format</code> bundle key. Parameter {0} is the display
 544   
      * name of the field.
 545   
      * 
 546   
      * @since 3.0
 547   
      */
 548   
 
 549  1
     public void setInvalidNumericFormatMessage(String string)
 550   
     {
 551  1
         _invalidNumericFormatMessage = string;
 552   
     }
 553   
 
 554   
     /**
 555   
      * Overrides the <code>invalid-int-format</code> bundle key. Parameter {0} is the display name
 556   
      * of the field.
 557   
      * 
 558   
      * @since 3.0
 559   
      */
 560   
 
 561  0
     public void setInvalidIntegerFormatMessage(String string)
 562   
     {
 563  0
         _invalidIntegerFormatMessage = string;
 564   
     }
 565   
 
 566   
     /** @since 3.0 */
 567   
 
 568  2
     protected String buildInvalidNumericFormatMessage(IFormComponent field)
 569   
     {
 570  2
         String pattern = getPattern(
 571   
                 getInvalidNumericFormatMessage(),
 572   
                 "invalid-numeric-format",
 573   
                 field.getPage().getLocale());
 574   
 
 575  2
         return formatString(pattern, field.getDisplayName());
 576   
     }
 577   
 
 578   
     /** @since 3.0 */
 579   
 
 580  1
     protected String buildInvalidIntegerFormatMessage(IFormComponent field)
 581   
     {
 582  1
         String pattern = getPattern(getInvalidIntegerFormatMessage(), "invalid-int-format", field
 583   
                 .getPage().getLocale());
 584   
 
 585  1
         return formatString(pattern, field.getDisplayName());
 586   
     }
 587   
 
 588   
     /**
 589   
      * Overrides the <code>number-range</code> bundle key. Parameter [0} is the display name of
 590   
      * the field. Parameter {1} is the minimum value. Parameter {2} is the maximum value.
 591   
      * 
 592   
      * @since 3.0
 593   
      */
 594   
 
 595  0
     public void setNumberRangeMessage(String string)
 596   
     {
 597  0
         _numberRangeMessage = string;
 598   
     }
 599   
 
 600  0
     protected String buildNumberRangeMessage(IFormComponent field)
 601   
     {
 602  0
         String pattern = getPattern(_numberRangeMessage, "number-range", field.getPage()
 603   
                 .getLocale());
 604   
 
 605  0
         return formatString(pattern, new Object[]
 606   
         { field.getDisplayName(), _minimum, _maximum });
 607   
     }
 608   
 
 609   
     /**
 610   
      * Overrides the <code>number-too-large</code> bundle key. Parameter {0} is the display name
 611   
      * of the field. Parameter {1} is the maximum allowed value.
 612   
      * 
 613   
      * @since 3.0
 614   
      */
 615   
 
 616  1
     public void setNumberTooLargeMessage(String string)
 617   
     {
 618  1
         _numberTooLargeMessage = string;
 619   
     }
 620   
 
 621   
     /** @since 3.0 */
 622   
 
 623  2
     protected String buildNumberTooLargeMessage(IFormComponent field)
 624   
     {
 625  2
         String pattern = getPattern(_numberTooLargeMessage, "number-too-large", field.getPage()
 626   
                 .getLocale());
 627   
 
 628  2
         return formatString(pattern, field.getDisplayName(), _maximum);
 629   
     }
 630   
 
 631   
     /**
 632   
      * Overrides the <code>number-too-small</code> bundle key. Parameter {0} is the display name
 633   
      * of the field. Parameter {1} is the minimum allowed value.
 634   
      * 
 635   
      * @since 3.0
 636   
      */
 637   
 
 638  1
     public void setNumberTooSmallMessage(String string)
 639   
     {
 640  1
         _numberTooSmallMessage = string;
 641   
     }
 642   
 
 643   
     /** @since 3.0 */
 644   
 
 645  2
     protected String buildNumberTooSmallMessage(IFormComponent field)
 646   
     {
 647  2
         String pattern = getPattern(_numberTooSmallMessage, "number-too-small", field.getPage()
 648   
                 .getLocale());
 649   
 
 650  2
         return formatString(pattern, field.getDisplayName(), _minimum);
 651   
     }
 652   
 
 653   
     /** @since 3.0 */
 654   
 
 655  1
     public boolean isIntegerNumber()
 656   
     {
 657  1
         NumberAdaptor result = (NumberAdaptor) _numberAdaptors.getAdapter(_valueTypeClass);
 658  1
         if (result == null)
 659  0
             return false;
 660   
 
 661  1
         return result.getNumberType() == NUMBER_TYPE_INTEGER;
 662   
     }
 663   
 }