Clover coverage report - Code Coverage for tapestry release 4.0-beta-10
Coverage timestamp: Sat Oct 8 2005 19:08:05 EDT
file stats: LOC: 476   Methods: 37
NCLOC: 315   Classes: 12
 
 Source file Conditionals Statements Methods TOTAL
NumberValidator.java 33.3% 73.2% 75.7% 67.4%
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.StrategyRegistry;
 24    import org.apache.hivemind.lib.util.StrategyRegistryImpl;
 25    import org.apache.hivemind.util.PropertyUtils;
 26    import org.apache.tapestry.IMarkupWriter;
 27    import org.apache.tapestry.IRequestCycle;
 28    import org.apache.tapestry.Tapestry;
 29    import org.apache.tapestry.form.IFormComponent;
 30   
 31    /**
 32    * Simple validation for standard number classes. This is probably insufficient for anything tricky
 33    * and application specific, such as parsing currency.
 34    *
 35    * @author Howard Lewis Ship
 36    * @since 1.0.8
 37    */
 38   
 39    public class NumberValidator extends AbstractNumericValidator
 40    {
 41    private static final Map TYPES = new HashMap();
 42   
 43    static
 44    {
 45  1 TYPES.put("boolean", boolean.class);
 46  1 TYPES.put("Boolean", Boolean.class);
 47  1 TYPES.put("java.lang.Boolean", Boolean.class);
 48  1 TYPES.put("char", char.class);
 49  1 TYPES.put("Character", Character.class);
 50  1 TYPES.put("java.lang.Character", Character.class);
 51  1 TYPES.put("short", short.class);
 52  1 TYPES.put("Short", Short.class);
 53  1 TYPES.put("java.lang.Short", Short.class);
 54  1 TYPES.put("int", int.class);
 55  1 TYPES.put("Integer", Integer.class);
 56  1 TYPES.put("java.lang.Integer", Integer.class);
 57  1 TYPES.put("long", long.class);
 58  1 TYPES.put("Long", Long.class);
 59  1 TYPES.put("java.lang.Long", Long.class);
 60  1 TYPES.put("float", float.class);
 61  1 TYPES.put("Float", Float.class);
 62  1 TYPES.put("java.lang.Float", Float.class);
 63  1 TYPES.put("byte", byte.class);
 64  1 TYPES.put("Byte", Byte.class);
 65  1 TYPES.put("java.lang.Byte", Byte.class);
 66  1 TYPES.put("double", double.class);
 67  1 TYPES.put("Double", Double.class);
 68  1 TYPES.put("java.lang.Double", Double.class);
 69  1 TYPES.put("java.math.BigInteger", BigInteger.class);
 70  1 TYPES.put("java.math.BigDecimal", BigDecimal.class);
 71    }
 72   
 73    private Class _valueTypeClass = int.class;
 74   
 75    private Number _minimum;
 76   
 77    private Number _maximum;
 78   
 79    private static StrategyRegistry _numberAdaptors = new StrategyRegistryImpl();
 80   
 81    public final static int NUMBER_TYPE_INTEGER = 0;
 82   
 83    public final static int NUMBER_TYPE_REAL = 1;
 84   
 85    /**
 86    * This class is not meant for use outside of NumberValidator; it is public only to fascilitate
 87    * some unit testing.
 88    */
 89    public static abstract class NumberStrategy
 90    {
 91    /**
 92    * Parses a non-empty {@link String}into the correct subclass of {@link Number}.
 93    *
 94    * @throws NumberFormatException
 95    * if the String can not be parsed.
 96    */
 97   
 98    abstract public Number parse(String value);
 99   
 100    /**
 101    * Indicates the type of the number represented -- integer or real. The information is used
 102    * to build the client-side validator. This method could return a boolean, but returns an
 103    * int to allow future extensions of the validator.
 104    *
 105    * @return one of the predefined number types
 106    */
 107    abstract public int getNumberType();
 108   
 109  15 public int compare(Number left, Number right)
 110    {
 111  15 if (!left.getClass().equals(right.getClass()))
 112  8 right = coerce(right);
 113   
 114  15 Comparable lc = (Comparable) left;
 115   
 116  15 return lc.compareTo(right);
 117    }
 118   
 119    /**
 120    * Invoked when comparing two Numbers of different types. The number is cooerced from its
 121    * ordinary type to the correct type for comparison.
 122    *
 123    * @since 3.0
 124    */
 125    protected abstract Number coerce(Number number);
 126    }
 127   
 128    private static abstract class IntegerNumberAdaptor extends NumberStrategy
 129    {
 130  5 public int getNumberType()
 131    {
 132  5 return NUMBER_TYPE_INTEGER;
 133    }
 134    }
 135   
 136    private static abstract class RealNumberAdaptor extends NumberStrategy
 137    {
 138  3 public int getNumberType()
 139    {
 140  3 return NUMBER_TYPE_REAL;
 141    }
 142    }
 143   
 144    private static class ByteAdaptor extends IntegerNumberAdaptor
 145    {
 146  1 public Number parse(String value)
 147    {
 148  1 return new Byte(value);
 149    }
 150   
 151  1 protected Number coerce(Number number)
 152    {
 153  1 return new Byte(number.byteValue());
 154    }
 155    }
 156   
 157    private static class ShortAdaptor extends IntegerNumberAdaptor
 158    {
 159  1 public Number parse(String value)
 160    {
 161  1 return new Short(value);
 162    }
 163   
 164  1 protected Number coerce(Number number)
 165    {
 166  1 return new Short(number.shortValue());
 167    }
 168    }
 169   
 170    private static class IntAdaptor extends IntegerNumberAdaptor
 171    {
 172  8 public Number parse(String value)
 173    {
 174  8 return new Integer(value);
 175    }
 176   
 177  1 protected Number coerce(Number number)
 178    {
 179  1 return new Integer(number.intValue());
 180    }
 181    }
 182   
 183    private static class LongAdaptor extends IntegerNumberAdaptor
 184    {
 185  1 public Number parse(String value)
 186    {
 187  1 return new Long(value);
 188    }
 189   
 190  1 protected Number coerce(Number number)
 191    {
 192  1 return new Long(number.longValue());
 193    }
 194    }
 195   
 196    private static class FloatAdaptor extends RealNumberAdaptor
 197    {
 198  1 public Number parse(String value)
 199    {
 200  1 return new Float(value);
 201    }
 202   
 203  1 protected Number coerce(Number number)
 204    {
 205  1 return new Float(number.floatValue());
 206    }
 207    }
 208   
 209    private static class DoubleAdaptor extends RealNumberAdaptor
 210    {
 211  1 public Number parse(String value)
 212    {
 213  1 return new Double(value);
 214    }
 215   
 216  1 protected Number coerce(Number number)
 217    {
 218  1 return new Double(number.doubleValue());
 219    }
 220    }
 221   
 222    private static class BigDecimalAdaptor extends RealNumberAdaptor
 223    {
 224  1 public Number parse(String value)
 225    {
 226  1 return new BigDecimal(value);
 227    }
 228   
 229  1 protected Number coerce(Number number)
 230    {
 231  1 return new BigDecimal(number.doubleValue());
 232    }
 233    }
 234   
 235    private static class BigIntegerAdaptor extends IntegerNumberAdaptor
 236    {
 237  1 public Number parse(String value)
 238    {
 239  1 return new BigInteger(value);
 240    }
 241   
 242  1 protected Number coerce(Number number)
 243    {
 244  1 return new BigInteger(number.toString());
 245    }
 246    }
 247   
 248    static
 249    {
 250  1 NumberStrategy byteAdaptor = new ByteAdaptor();
 251  1 NumberStrategy shortAdaptor = new ShortAdaptor();
 252  1 NumberStrategy intAdaptor = new IntAdaptor();
 253  1 NumberStrategy longAdaptor = new LongAdaptor();
 254  1 NumberStrategy floatAdaptor = new FloatAdaptor();
 255  1 NumberStrategy doubleAdaptor = new DoubleAdaptor();
 256   
 257  1 _numberAdaptors.register(Byte.class, byteAdaptor);
 258  1 _numberAdaptors.register(byte.class, byteAdaptor);
 259  1 _numberAdaptors.register(Short.class, shortAdaptor);
 260  1 _numberAdaptors.register(short.class, shortAdaptor);
 261  1 _numberAdaptors.register(Integer.class, intAdaptor);
 262  1 _numberAdaptors.register(int.class, intAdaptor);
 263  1 _numberAdaptors.register(Long.class, longAdaptor);
 264  1 _numberAdaptors.register(long.class, longAdaptor);
 265  1 _numberAdaptors.register(Float.class, floatAdaptor);
 266  1 _numberAdaptors.register(float.class, floatAdaptor);
 267  1 _numberAdaptors.register(Double.class, doubleAdaptor);
 268  1 _numberAdaptors.register(double.class, doubleAdaptor);
 269   
 270  1 _numberAdaptors.register(BigDecimal.class, new BigDecimalAdaptor());
 271  1 _numberAdaptors.register(BigInteger.class, new BigIntegerAdaptor());
 272    }
 273   
 274  24 public NumberValidator()
 275    {
 276   
 277    }
 278   
 279    /**
 280    * Initializes the NumberValidator with properties defined by the initializer.
 281    *
 282    * @since 4.0
 283    */
 284   
 285  0 public NumberValidator(String initializer)
 286    {
 287  0 PropertyUtils.configureProperties(this, initializer);
 288    }
 289   
 290  13 public String toString(IFormComponent field, Object value)
 291    {
 292  13 if (value == null)
 293  0 return null;
 294   
 295  13 if (getZeroIsNull())
 296    {
 297  0 Number number = (Number) value;
 298   
 299  0 if (number.doubleValue() == 0.0)
 300  0 return null;
 301    }
 302   
 303  13 return value.toString();
 304    }
 305   
 306  15 private NumberStrategy getStrategy(IFormComponent field)
 307    {
 308  15 NumberStrategy result = getStrategy(_valueTypeClass);
 309   
 310  15 if (result == null)
 311  0 throw new ApplicationRuntimeException(Tapestry.format(
 312    "NumberValidator.no-adaptor-for-field",
 313    field,
 314    _valueTypeClass.getName()));
 315   
 316  15 return result;
 317    }
 318   
 319    /**
 320    * Returns an strategy for the given type.
 321    * <p>
 322    * Note: this method exists only for testing purposes. It is not meant to be invoked by user
 323    * code and is subject to change at any time.
 324    *
 325    * @param type
 326    * the type (a Number subclass) for which to return an adaptor
 327    * @return the adaptor, or null if no such adaptor may be found
 328    * @since 3.0
 329    */
 330  31 public static NumberStrategy getStrategy(Class type)
 331    {
 332  31 return (NumberStrategy) _numberAdaptors.getStrategy(type);
 333    }
 334   
 335  15 public Object toObject(IFormComponent field, String value) throws ValidatorException
 336    {
 337  15 if (checkRequired(field, value))
 338  0 return null;
 339   
 340  15 NumberStrategy adaptor = getStrategy(field);
 341  15 Number result = null;
 342   
 343  15 try
 344    {
 345  15 result = adaptor.parse(value);
 346    }
 347    catch (NumberFormatException ex)
 348    {
 349  2 throw new ValidatorException(buildInvalidNumericFormatMessage(field),
 350    ValidationConstraint.NUMBER_FORMAT);
 351    }
 352   
 353  13 if (_minimum != null && adaptor.compare(result, _minimum) < 0)
 354  2 throw new ValidatorException(buildNumberTooSmallMessage(field, _minimum),
 355    ValidationConstraint.TOO_SMALL);
 356   
 357  11 if (_maximum != null && adaptor.compare(result, _maximum) > 0)
 358  2 throw new ValidatorException(buildNumberTooLargeMessage(field, _maximum),
 359    ValidationConstraint.TOO_LARGE);
 360   
 361  9 return result;
 362    }
 363   
 364  0 public Number getMaximum()
 365    {
 366  0 return _maximum;
 367    }
 368   
 369  0 public boolean getHasMaximum()
 370    {
 371  0 return _maximum != null;
 372    }
 373   
 374  4 public void setMaximum(Number maximum)
 375    {
 376  4 _maximum = maximum;
 377    }
 378   
 379  0 public Number getMinimum()
 380    {
 381  0 return _minimum;
 382    }
 383   
 384  0 public boolean getHasMinimum()
 385    {
 386  0 return _minimum != null;
 387    }
 388   
 389  4 public void setMinimum(Number minimum)
 390    {
 391  4 _minimum = minimum;
 392    }
 393   
 394    /**
 395    * @since 2.2
 396    */
 397   
 398  0 public void renderValidatorContribution(IFormComponent field, IMarkupWriter writer,
 399    IRequestCycle cycle)
 400    {
 401  0 if (!isClientScriptingEnabled())
 402  0 return;
 403   
 404  0 if (!(isRequired() || _minimum != null || _maximum != null))
 405  0 return;
 406   
 407  0 Map symbols = new HashMap();
 408   
 409  0 if (isRequired())
 410  0 symbols.put("requiredMessage", buildRequiredMessage(field));
 411   
 412  0 if (isIntegerNumber())
 413  0 symbols.put("formatMessage", buildInvalidIntegerFormatMessage(field));
 414    else
 415  0 symbols.put("formatMessage", buildInvalidNumericFormatMessage(field));
 416   
 417  0 if (_minimum != null || _maximum != null)
 418  0 symbols.put("rangeMessage", buildRangeMessage(field, _minimum, _maximum));
 419   
 420  0 processValidatorScript(getScriptPath(), cycle, field, symbols);
 421    }
 422   
 423    /**
 424    * Sets the value type from a string type name. The name may be a scalar numeric type, a fully
 425    * qualified class name, or the name of a numeric wrapper type from java.lang (with the package
 426    * name omitted).
 427    *
 428    * @since 3.0
 429    */
 430   
 431  0 public void setValueType(String typeName)
 432    {
 433  0 Class typeClass = (Class) TYPES.get(typeName);
 434   
 435  0 if (typeClass == null)
 436  0 throw new ApplicationRuntimeException(Tapestry.format(
 437    "NumberValidator.unknown-type",
 438    typeName));
 439   
 440  0 _valueTypeClass = typeClass;
 441    }
 442   
 443    /** @since 3.0 * */
 444   
 445  15 public void setValueTypeClass(Class valueTypeClass)
 446    {
 447  15 _valueTypeClass = valueTypeClass;
 448    }
 449   
 450    /**
 451    * Returns the value type to convert strings back into. The default is int.
 452    *
 453    * @since 3.0
 454    */
 455   
 456  0 public Class getValueTypeClass()
 457    {
 458  0 return _valueTypeClass;
 459    }
 460   
 461    /** @since 3.0 */
 462   
 463  0 public boolean isIntegerNumber()
 464    {
 465  0 NumberStrategy strategy = (NumberStrategy) _numberAdaptors.getStrategy(_valueTypeClass);
 466  0 if (strategy == null)
 467  0 return false;
 468   
 469  0 return strategy.getNumberType() == NUMBER_TYPE_INTEGER;
 470    }
 471   
 472  24 protected String getDefaultScriptPath()
 473    {
 474  24 return "/org/apache/tapestry/valid/NumberValidator.script";
 475    }
 476    }