| // Copyright 2004, 2005 The Apache Software Foundation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package org.apache.tapestry.valid; |
| |
| import org.apache.hivemind.ApplicationRuntimeException; |
| import org.apache.hivemind.lib.util.StrategyRegistry; |
| import org.apache.hivemind.lib.util.StrategyRegistryImpl; |
| import org.apache.hivemind.util.PropertyUtils; |
| import org.apache.tapestry.IMarkupWriter; |
| import org.apache.tapestry.IRequestCycle; |
| import org.apache.tapestry.Tapestry; |
| import org.apache.tapestry.form.IFormComponent; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| /** |
| * Simple validation for standard number classes. This is probably insufficient for anything tricky |
| * and application specific, such as parsing currency. |
| * |
| * @author Howard Lewis Ship |
| * @since 1.0.8 |
| */ |
| |
| public class NumberValidator extends AbstractNumericValidator |
| { |
| public static final int NUMBER_TYPE_INTEGER = 0; |
| |
| public static final int NUMBER_TYPE_REAL = 1; |
| |
| private static final Map TYPES = new HashMap(); |
| |
| private static StrategyRegistry _numberAdaptors = new StrategyRegistryImpl(); |
| |
| static |
| { |
| TYPES.put("boolean", boolean.class); |
| TYPES.put("Boolean", Boolean.class); |
| TYPES.put("java.lang.Boolean", Boolean.class); |
| TYPES.put("char", char.class); |
| TYPES.put("Character", Character.class); |
| TYPES.put("java.lang.Character", Character.class); |
| TYPES.put("short", short.class); |
| TYPES.put("Short", Short.class); |
| TYPES.put("java.lang.Short", Short.class); |
| TYPES.put("int", int.class); |
| TYPES.put("Integer", Integer.class); |
| TYPES.put("java.lang.Integer", Integer.class); |
| TYPES.put("long", long.class); |
| TYPES.put("Long", Long.class); |
| TYPES.put("java.lang.Long", Long.class); |
| TYPES.put("float", float.class); |
| TYPES.put("Float", Float.class); |
| TYPES.put("java.lang.Float", Float.class); |
| TYPES.put("byte", byte.class); |
| TYPES.put("Byte", Byte.class); |
| TYPES.put("java.lang.Byte", Byte.class); |
| TYPES.put("double", double.class); |
| TYPES.put("Double", Double.class); |
| TYPES.put("java.lang.Double", Double.class); |
| TYPES.put("java.math.BigInteger", BigInteger.class); |
| TYPES.put("java.math.BigDecimal", BigDecimal.class); |
| } |
| |
| private Class _valueTypeClass = int.class; |
| |
| private Number _minimum; |
| |
| private Number _maximum; |
| |
| /** |
| * This class is not meant for use outside of NumberValidator; it is public only to fascilitate |
| * some unit testing. |
| */ |
| public abstract static class NumberStrategy |
| { |
| /** |
| * Parses a non-empty {@link String}into the correct subclass of {@link Number}. |
| * |
| * @throws NumberFormatException |
| * if the String can not be parsed. |
| */ |
| |
| public abstract Number parse(String value); |
| |
| /** |
| * Indicates the type of the number represented -- integer or real. The information is used |
| * to build the client-side validator. This method could return a boolean, but returns an |
| * int to allow future extensions of the validator. |
| * |
| * @return one of the predefined number types |
| */ |
| public abstract int getNumberType(); |
| |
| public int compare(Number left, Number right) |
| { |
| Number comparisonRight = right; |
| if (!left.getClass().equals(comparisonRight.getClass())) |
| comparisonRight = coerce(comparisonRight); |
| |
| Comparable lc = (Comparable) left; |
| |
| return lc.compareTo(comparisonRight); |
| } |
| |
| /** |
| * Invoked when comparing two Numbers of different types. The number is cooerced from its |
| * ordinary type to the correct type for comparison. |
| * |
| * @since 3.0 |
| */ |
| protected abstract Number coerce(Number number); |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private abstract static class IntegerNumberAdaptor extends NumberStrategy |
| { |
| public int getNumberType() |
| { |
| return NUMBER_TYPE_INTEGER; |
| } |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private abstract static class RealNumberAdaptor extends NumberStrategy |
| { |
| public int getNumberType() |
| { |
| return NUMBER_TYPE_REAL; |
| } |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private static class ByteAdaptor extends IntegerNumberAdaptor |
| { |
| public Number parse(String value) |
| { |
| return new Byte(value); |
| } |
| |
| protected Number coerce(Number number) |
| { |
| return new Byte(number.byteValue()); |
| } |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private static class ShortAdaptor extends IntegerNumberAdaptor |
| { |
| public Number parse(String value) |
| { |
| return new Short(value); |
| } |
| |
| protected Number coerce(Number number) |
| { |
| return new Short(number.shortValue()); |
| } |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private static class IntAdaptor extends IntegerNumberAdaptor |
| { |
| public Number parse(String value) |
| { |
| return new Integer(value); |
| } |
| |
| protected Number coerce(Number number) |
| { |
| return new Integer(number.intValue()); |
| } |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private static class LongAdaptor extends IntegerNumberAdaptor |
| { |
| public Number parse(String value) |
| { |
| return new Long(value); |
| } |
| |
| protected Number coerce(Number number) |
| { |
| return new Long(number.longValue()); |
| } |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private static class FloatAdaptor extends RealNumberAdaptor |
| { |
| public Number parse(String value) |
| { |
| return new Float(value); |
| } |
| |
| protected Number coerce(Number number) |
| { |
| return new Float(number.floatValue()); |
| } |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private static class DoubleAdaptor extends RealNumberAdaptor |
| { |
| public Number parse(String value) |
| { |
| return new Double(value); |
| } |
| |
| protected Number coerce(Number number) |
| { |
| return new Double(number.doubleValue()); |
| } |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private static class BigDecimalAdaptor extends RealNumberAdaptor |
| { |
| public Number parse(String value) |
| { |
| return new BigDecimal(value); |
| } |
| |
| protected Number coerce(Number number) |
| { |
| return new BigDecimal(number.doubleValue()); |
| } |
| } |
| |
| /** |
| * Integer adaptor. |
| */ |
| private static class BigIntegerAdaptor extends IntegerNumberAdaptor |
| { |
| public Number parse(String value) |
| { |
| return new BigInteger(value); |
| } |
| |
| protected Number coerce(Number number) |
| { |
| return new BigInteger(number.toString()); |
| } |
| } |
| |
| static |
| { |
| NumberStrategy byteAdaptor = new ByteAdaptor(); |
| NumberStrategy shortAdaptor = new ShortAdaptor(); |
| NumberStrategy intAdaptor = new IntAdaptor(); |
| NumberStrategy longAdaptor = new LongAdaptor(); |
| NumberStrategy floatAdaptor = new FloatAdaptor(); |
| NumberStrategy doubleAdaptor = new DoubleAdaptor(); |
| |
| _numberAdaptors.register(Byte.class, byteAdaptor); |
| _numberAdaptors.register(byte.class, byteAdaptor); |
| _numberAdaptors.register(Short.class, shortAdaptor); |
| _numberAdaptors.register(short.class, shortAdaptor); |
| _numberAdaptors.register(Integer.class, intAdaptor); |
| _numberAdaptors.register(int.class, intAdaptor); |
| _numberAdaptors.register(Long.class, longAdaptor); |
| _numberAdaptors.register(long.class, longAdaptor); |
| _numberAdaptors.register(Float.class, floatAdaptor); |
| _numberAdaptors.register(float.class, floatAdaptor); |
| _numberAdaptors.register(Double.class, doubleAdaptor); |
| _numberAdaptors.register(double.class, doubleAdaptor); |
| |
| _numberAdaptors.register(BigDecimal.class, new BigDecimalAdaptor()); |
| _numberAdaptors.register(BigInteger.class, new BigIntegerAdaptor()); |
| } |
| |
| public NumberValidator() |
| { |
| |
| } |
| |
| /** |
| * Initializes the NumberValidator with properties defined by the initializer. |
| * |
| * @since 4.0 |
| */ |
| |
| public NumberValidator(String initializer) |
| { |
| PropertyUtils.configureProperties(this, initializer); |
| } |
| |
| public String toString(IFormComponent field, Object value) |
| { |
| if (value == null) |
| return null; |
| |
| if (getZeroIsNull()) |
| { |
| Number number = (Number) value; |
| |
| if (number.doubleValue() == 0.0) |
| return null; |
| } |
| |
| return value.toString(); |
| } |
| |
| private NumberStrategy getStrategy(IFormComponent field) |
| { |
| NumberStrategy result = getStrategy(_valueTypeClass); |
| |
| if (result == null) |
| throw new ApplicationRuntimeException(Tapestry.format("NumberValidator.no-adaptor-for-field", |
| field, |
| _valueTypeClass.getName())); |
| |
| return result; |
| } |
| |
| /** |
| * Returns an strategy for the given type. |
| * <p> |
| * Note: this method exists only for testing purposes. It is not meant to be invoked by user |
| * code and is subject to change at any time. |
| * |
| * @param type |
| * the type (a Number subclass) for which to return an adaptor |
| * @return the adaptor, or null if no such adaptor may be found |
| * @since 3.0 |
| */ |
| public static NumberStrategy getStrategy(Class type) |
| { |
| return (NumberStrategy) _numberAdaptors.getStrategy(type); |
| } |
| |
| public Object toObject(IFormComponent field, String value) throws ValidatorException |
| { |
| if (checkRequired(field, value)) |
| return null; |
| |
| NumberStrategy adaptor = getStrategy(field); |
| Number result = null; |
| |
| try |
| { |
| result = adaptor.parse(value); |
| } |
| catch (NumberFormatException ex) |
| { |
| throw new ValidatorException(buildInvalidNumericFormatMessage(field), |
| ValidationConstraint.NUMBER_FORMAT); |
| } |
| |
| if (_minimum != null && adaptor.compare(result, _minimum) < 0) |
| throw new ValidatorException(buildNumberTooSmallMessage(field, _minimum), |
| ValidationConstraint.TOO_SMALL); |
| |
| if (_maximum != null && adaptor.compare(result, _maximum) > 0) |
| throw new ValidatorException(buildNumberTooLargeMessage(field, _maximum), |
| ValidationConstraint.TOO_LARGE); |
| |
| return result; |
| } |
| |
| public Number getMaximum() |
| { |
| return _maximum; |
| } |
| |
| public boolean getHasMaximum() |
| { |
| return _maximum != null; |
| } |
| |
| public void setMaximum(Number maximum) |
| { |
| _maximum = maximum; |
| } |
| |
| public Number getMinimum() |
| { |
| return _minimum; |
| } |
| |
| public boolean getHasMinimum() |
| { |
| return _minimum != null; |
| } |
| |
| public void setMinimum(Number minimum) |
| { |
| _minimum = minimum; |
| } |
| |
| /** |
| * @since 2.2 |
| */ |
| |
| public void renderValidatorContribution(IFormComponent field, IMarkupWriter writer, |
| IRequestCycle cycle) |
| { |
| if (!isClientScriptingEnabled()) |
| return; |
| |
| if (!(isRequired() || _minimum != null || _maximum != null)) |
| return; |
| |
| Map symbols = new HashMap(); |
| |
| if (isRequired()) |
| symbols.put("requiredMessage", buildRequiredMessage(field)); |
| |
| if (isIntegerNumber()) |
| symbols.put("formatMessage", buildInvalidIntegerFormatMessage(field)); |
| else |
| symbols.put("formatMessage", buildInvalidNumericFormatMessage(field)); |
| |
| if (_minimum != null || _maximum != null) |
| symbols.put("rangeMessage", buildRangeMessage(field, _minimum, _maximum)); |
| |
| processValidatorScript(getScriptPath(), cycle, field, symbols); |
| } |
| |
| /** |
| * Sets the value type from a string type name. The name may be a scalar numeric type, a fully |
| * qualified class name, or the name of a numeric wrapper type from java.lang (with the package |
| * name omitted). |
| * |
| * @since 3.0 |
| */ |
| |
| public void setValueType(String typeName) |
| { |
| Class typeClass = (Class) TYPES.get(typeName); |
| |
| if (typeClass == null) |
| throw new ApplicationRuntimeException(Tapestry.format("NumberValidator.unknown-type", typeName)); |
| |
| _valueTypeClass = typeClass; |
| } |
| |
| /** @since 3.0 * */ |
| |
| public void setValueTypeClass(Class valueTypeClass) |
| { |
| _valueTypeClass = valueTypeClass; |
| } |
| |
| /** |
| * Returns the value type to convert strings back into. The default is int. |
| * |
| * @since 3.0 |
| */ |
| |
| public Class getValueTypeClass() |
| { |
| return _valueTypeClass; |
| } |
| |
| /** @since 3.0 */ |
| |
| public boolean isIntegerNumber() |
| { |
| NumberStrategy strategy = (NumberStrategy) _numberAdaptors.getStrategy(_valueTypeClass); |
| if (strategy == null) |
| return false; |
| |
| return strategy.getNumberType() == NUMBER_TYPE_INTEGER; |
| } |
| |
| protected String getDefaultScriptPath() |
| { |
| return "/org/apache/tapestry/valid/NumberValidator.script"; |
| } |
| } |