Author: hlship
Date: Thu Dec 21 13:29:39 2006
New Revision: 489466

URL: http://svn.apache.org/viewvc?view=rev&rev=489466
Log:
Extend the FieldValidatorSource to provide a field validators based on a simple 
text specification.

Added:
    
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java
Modified:
    
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
    
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
    
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java
    
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
    
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
    
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java

Modified: 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
--- 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
 (original)
+++ 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
 Thu Dec 21 13:29:39 2006
@@ -14,9 +14,11 @@
 
 package org.apache.tapestry.internal.services;
 
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
 import static org.apache.tapestry.ioc.internal.util.Defense.cast;
 import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
 
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
@@ -80,6 +82,34 @@
         };
     }
 
+    public FieldValidator createValidators(Field field, String specification)
+    {
+        List<ValidatorSpecification> specs = parse(specification);
+
+        List<FieldValidator> fieldValidators = newList();
+
+        for (ValidatorSpecification spec : specs)
+        {
+            fieldValidators.add(createValidator(field, 
spec.getValidatorType(), spec
+                    .getConstraintValue()));
+        }
+
+        if (fieldValidators.size() == 1)
+            return fieldValidators.get(0);
+
+        final FieldValidator[] array = fieldValidators.toArray(new 
FieldValidator[0]);
+
+        return new FieldValidator()
+        {
+            @SuppressWarnings("unchecked")
+            public void check(Object value) throws ValidationException
+            {
+                for (FieldValidator fv : array)
+                    fv.check(value);
+            }
+        };
+    }
+
     @SuppressWarnings("unchecked")
     private Object coerceConstraintValue(String constraintValue, Class 
constraintType)
     {
@@ -89,4 +119,177 @@
         return _typeCoercer.coerce(constraintValue, constraintType);
     }
 
+    /**
+     * A code defining what the parser is looking for.
+     */
+    enum State {
+
+        /** The start of a validator type. */
+        TYPE_START,
+        /** The end of a validator type. */
+        TYPE_END,
+        /** Equals sign after a validator type, or a comma. */
+        EQUALS_OR_COMMA,
+        /** The start of a constraint value. */
+        VALUE_START,
+        /** The end of the constraint value. */
+        VALUE_END,
+        /** The comma after a constraint value. */
+        COMMA
+    };
+
+    static List<ValidatorSpecification> parse(String specification)
+    {
+        List<ValidatorSpecification> result = newList();
+
+        char[] input = specification.toCharArray();
+
+        int cursor = 0;
+        int start = -1;
+
+        String type = null;
+        String value = null;
+        boolean skipWhitespace = true;
+        State state = State.TYPE_START;
+
+        while (cursor < input.length)
+        {
+            char ch = input[cursor];
+
+            if (skipWhitespace && Character.isWhitespace(ch))
+            {
+                cursor++;
+                continue;
+            }
+
+            skipWhitespace = false;
+
+            switch (state)
+            {
+
+                case TYPE_START:
+
+                    if (Character.isLetter(ch))
+                    {
+                        start = cursor;
+                        state = State.TYPE_END;
+                        break;
+                    }
+
+                    parseError(cursor, specification);
+
+                case TYPE_END:
+
+                    if (Character.isLetter(ch))
+                    {
+                        break;
+                    }
+
+                    type = specification.substring(start, cursor);
+
+                    skipWhitespace = true;
+                    state = State.EQUALS_OR_COMMA;
+                    continue;
+
+                case EQUALS_OR_COMMA:
+
+                    if (ch == '=')
+                    {
+                        skipWhitespace = true;
+                        state = State.VALUE_START;
+                        break;
+                    }
+
+                    if (ch == ',')
+                    {
+                        result.add(new ValidatorSpecification(type));
+                        type = null;
+                        state = State.COMMA;
+                        continue;
+                    }
+
+                    parseError(cursor, specification);
+
+                case VALUE_START:
+
+                    start = cursor;
+                    state = State.VALUE_END;
+                    break;
+
+                case VALUE_END:
+
+                    // The value ends when we hit whitespace or a comma
+
+                    if (Character.isWhitespace(ch) || ch == ',')
+                    {
+                        value = specification.substring(start, cursor);
+
+                        result.add(new ValidatorSpecification(type, value));
+                        type = null;
+                        value = null;
+
+                        skipWhitespace = true;
+                        state = State.COMMA;
+                        continue;
+                    }
+
+                    break;
+
+                case COMMA:
+
+                    if (ch == ',')
+                    {
+                        skipWhitespace = true;
+                        state = State.TYPE_START;
+                        break;
+
+                    }
+
+                    parseError(cursor, specification);
+            } // case
+
+            cursor++;
+        } // while
+
+        // cursor is now one character past end of string.
+        // Cleanup whatever state we were in the middle of.
+
+        switch (state)
+        {
+            case TYPE_END:
+
+                type = specification.substring(start);
+
+            case EQUALS_OR_COMMA:
+
+                result.add(new ValidatorSpecification(type));
+                break;
+
+            // Case when the specification ends with an equals sign.
+
+            case VALUE_START:
+                result.add(new ValidatorSpecification(type, ""));
+                break;
+
+            case VALUE_END:
+                value = specification.substring(start);
+
+                result.add(new ValidatorSpecification(type, value));
+                break;
+
+            // For better or worse, ending the string with a comma is valid.
+
+            default:
+
+        }
+
+        return result;
+    }
+
+    private static void parseError(int cursor, String specification)
+    {
+        throw new 
RuntimeException(ServicesMessages.validatorSpecificationParseError(
+                cursor,
+                specification));
+    }
 }

Modified: 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
--- 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
 (original)
+++ 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
 Thu Dec 21 13:29:39 2006
@@ -278,4 +278,13 @@
         return MESSAGES.format("unknown-validator-type", validatorType, 
InternalUtils
                 .join(knownValidatorTypes));
     }
+
+    static String validatorSpecificationParseError(int cursor, String 
specification)
+    {
+        return MESSAGES.format(
+                "validator-specification-parse-error",
+                specification.charAt(cursor),
+                cursor + 1,
+                specification);
+    }
 }

Added: 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java?view=auto&rev=489466
==============================================================================
--- 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java
 (added)
+++ 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java
 Thu Dec 21 13:29:39 2006
@@ -0,0 +1,56 @@
+package org.apache.tapestry.internal.services;
+
+/** Validator type and constraint values parsed from a validator 
specification. */
+class ValidatorSpecification
+{
+    private final String _validatorType;
+
+    private final String _constraintValue;
+
+    public ValidatorSpecification(String validatorType)
+    {
+        this(validatorType, null);
+    }
+
+    public ValidatorSpecification(String validatorType, String constraintValue)
+    {
+        _validatorType = validatorType;
+        _constraintValue = constraintValue;
+    }
+
+    public String getConstraintValue()
+    {
+        return _constraintValue;
+    }
+
+    public String getValidatorType()
+    {
+        return _validatorType;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("ValidatorSpecification[%s %s]", _validatorType, 
_constraintValue);
+    }
+
+    @Override
+    public boolean equals(Object other)
+    {
+        if (other == null || other.getClass() != getClass())
+            return false;
+
+        ValidatorSpecification ov = (ValidatorSpecification) other;
+
+        if (!_validatorType.equals(ov._validatorType))
+            return false;
+
+        if (_constraintValue == ov._constraintValue)
+            return true;
+
+        if (_constraintValue == null)
+            return false;
+
+        return _constraintValue.equals(ov._constraintValue);
+    }
+}
\ No newline at end of file

Modified: 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
--- 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java
 (original)
+++ 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/FieldValidatorSource.java
 Thu Dec 21 13:29:39 2006
@@ -37,4 +37,15 @@
      * @return the field validator for the field
      */
     FieldValidator createValidator(Field field, String validatorType, String 
constraintValue);
+
+    /**
+     * Creates a set of validators. The specification is a string used to 
identify and configure the
+     * individual validators. The specification is a comma-separated list of 
terms. Each term is a
+     * validator type name and an optional constraint value (seperated by an 
equals sign).
+     * 
+     * @param field
+     * @param specification
+     * @return a composite field validator
+     */
+    FieldValidator createValidators(Field field, String specification);
 }

Modified: 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
--- 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
 (original)
+++ 
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
 Thu Dec 21 13:29:39 2006
@@ -28,6 +28,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.tapestry.ComponentEventHandler;
 import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.Validator;
 import org.apache.tapestry.annotations.AfterRender;
 import org.apache.tapestry.annotations.AfterRenderBody;
 import org.apache.tapestry.annotations.AfterRenderTemplate;
@@ -64,6 +65,7 @@
 import org.apache.tapestry.internal.services.DefaultInjectionProvider;
 import org.apache.tapestry.internal.services.EnvironmentImpl;
 import org.apache.tapestry.internal.services.EnvironmentalWorker;
+import org.apache.tapestry.internal.services.FieldValidatorSourceImpl;
 import org.apache.tapestry.internal.services.HeartbeatImpl;
 import org.apache.tapestry.internal.services.InfrastructureImpl;
 import org.apache.tapestry.internal.services.InfrastructureManagerImpl;
@@ -117,6 +119,7 @@
 import org.apache.tapestry.ioc.internal.util.InternalUtils;
 import org.apache.tapestry.ioc.services.ChainBuilder;
 import org.apache.tapestry.ioc.services.ClassFactory;
+import org.apache.tapestry.ioc.services.FieldValidatorSource;
 import org.apache.tapestry.ioc.services.PipelineBuilder;
 import org.apache.tapestry.ioc.services.PropertyShadowBuilder;
 import org.apache.tapestry.ioc.services.StrategyBuilder;
@@ -411,9 +414,7 @@
      */
     public static void contributeInfrastructure(
             Configuration<InfrastructureContribution> configuration, 
ServiceLocator locator,
-            @InjectService("Request")
-            Request request, @InjectService("Response")
-            Response response, @InjectService("tapestry.ioc.TypeCoercer")
+            @InjectService("tapestry.ioc.TypeCoercer")
             TypeCoercer typeCoercer)
     {
         add(
@@ -430,7 +431,8 @@
                 ClasspathAssetAliasManager.class,
                 Request.class,
                 Response.class,
-                ValidationMessagesSource.class);
+                ValidationMessagesSource.class,
+                FieldValidatorSource.class);
 
         configuration.add(new InfrastructureContribution("TypeCoercer", 
typeCoercer));
     }
@@ -855,5 +857,13 @@
             MappedConfiguration<String, String> configuration)
     {
         configuration.add("tapestry/", "org/apache/tapestry/");
+    }
+
+    public static FieldValidatorSource buildFieldValidatorSource(
+            @Inject("infrastructure:ValidationMessagesSource")
+            ValidationMessagesSource messagesSource, 
@Inject("infrastructure:TypeCoercer")
+            TypeCoercer typeCoercer, Map<String, Validator> configuration)
+    {
+        return new FieldValidatorSourceImpl(messagesSource, typeCoercer, 
configuration);
     }
 }

Modified: 
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
--- 
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
 (original)
+++ 
tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
 Thu Dec 21 13:29:39 2006
@@ -64,4 +64,5 @@
 asset-does-not-exist=Unable to locate asset '%s' (the file does not exist).
 wrong-asset-digest=The asset digest in the request does not match the actual 
digest for asset '%s'. This indicates that the content of the asset has changed 
between requests. 
 component-not-assignable-to-field=Component %s is not assignable to field %s 
(of type %s).
-unknown-validator-type=Unknown validator type '%s'.  Configured validators are 
%s.
\ No newline at end of file
+unknown-validator-type=Unknown validator type '%s'.  Configured validators are 
%s.
+validator-specification-parse-error=Unexpected character '%s' at position %d 
of input string: %s
\ No newline at end of file

Modified: 
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java?view=diff&rev=489466&r1=489465&r2=489466
==============================================================================
--- 
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java
 (original)
+++ 
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java
 Thu Dec 21 13:29:39 2006
@@ -17,6 +17,8 @@
 import static java.util.Collections.singletonMap;
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
@@ -107,6 +109,83 @@
 
     @SuppressWarnings("unchecked")
     @Test
+    public void single_validator_via_specification() throws Exception
+    {
+        ValidationMessagesSource messagesSource = 
newValidationMessagesSource();
+        Validator validator = newValidator();
+        TypeCoercer coercer = newTypeCoercer();
+        FieldComponent field = newFieldComponent();
+        Messages messages = newMock(Messages.class);
+        Object inputValue = new Object();
+        ComponentResources resources = newComponentResources();
+
+        Map<String, Validator> map = singletonMap("required", validator);
+
+        train_getConstraintType(validator, null);
+
+        train_getComponentResources(field, resources);
+        train_getLocale(resources, Locale.FRENCH);
+
+        train_getValidationMessages(messagesSource, Locale.FRENCH, messages);
+
+        validator.check(field, null, messages, inputValue);
+
+        replay();
+
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, map);
+
+        FieldValidator fieldValidator = source.createValidators(field, 
"required");
+
+        fieldValidator.check(inputValue);
+
+        verify();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void multiple_validators_via_specification() throws Exception
+    {
+        ValidationMessagesSource messagesSource = 
newValidationMessagesSource();
+        Validator required = newValidator();
+        Validator minLength = newValidator();
+        TypeCoercer coercer = newTypeCoercer();
+        FieldComponent field = newFieldComponent();
+        Messages messages = newMock(Messages.class);
+        Object inputValue = new Object();
+        ComponentResources resources = newComponentResources();
+        Integer fifteen = 15;
+
+        Map<String, Validator> map = newMap();
+
+        map.put("required", required);
+        map.put("minLength", minLength);
+
+        train_getConstraintType(required, null);
+        train_getConstraintType(minLength, Integer.class);
+
+        train_getComponentResources(field, resources);
+        train_getLocale(resources, Locale.FRENCH);
+
+        train_getValidationMessages(messagesSource, Locale.FRENCH, messages);
+
+        train_coerce(coercer, "15", Integer.class, fifteen);
+
+        required.check(field, null, messages, inputValue);
+        minLength.check(field, fifteen, messages, inputValue);
+
+        replay();
+
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, map);
+
+        FieldValidator fieldValidator = source.createValidators(field, 
"required,minLength=15");
+
+        fieldValidator.check(inputValue);
+
+        verify();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
     public void validator_with_constraint() throws Exception
     {
         ValidationMessagesSource messagesSource = 
newValidationMessagesSource();
@@ -145,5 +224,90 @@
     private FieldComponent newFieldComponent()
     {
         return newMock(FieldComponent.class);
+    }
+
+    private void test(String specification, ValidatorSpecification... expected)
+    {
+        List<ValidatorSpecification> specs = 
FieldValidatorSourceImpl.parse(specification);
+
+        assertEquals(specs, Arrays.asList(expected));
+    }
+
+    @Test
+    public void parse_simple_type_list()
+    {
+        test(
+                "required,email",
+                new ValidatorSpecification("required", null),
+                new ValidatorSpecification("email", null));
+    }
+
+    @Test
+    public void parse_single_type()
+    {
+        test("required", new ValidatorSpecification("required", null));
+    }
+
+    @Test
+    public void ignore_whitespace_around_type_name()
+    {
+        test(
+                "  required  ,  email  ",
+                new ValidatorSpecification("required", null),
+                new ValidatorSpecification("email", null));
+    }
+
+    @Test
+    public void parse_simple_type_with_value()
+    {
+        test(
+                "minLength=5,sameAs=otherComponentId",
+                new ValidatorSpecification("minLength", "5"),
+                new ValidatorSpecification("sameAs", "otherComponentId"));
+    }
+
+    @Test
+    public void whitespace_ignored_around_value()
+    {
+        test("minLength=  5 , sameAs  = otherComponentId ", new 
ValidatorSpecification("minLength",
+                "5"), new ValidatorSpecification("sameAs", 
"otherComponentId"));
+    }
+
+    @Test
+    public void dangling_equals_sign_is_empty_string_value()
+    {
+        test("minLength=  ", new ValidatorSpecification("minLength", ""));
+    }
+
+    @Test
+    public void unexpected_character_not_a_comma()
+    {
+        try
+        {
+            test("required.email");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Unexpected character '.' at position 9 of input string: 
required.email");
+        }
+    }
+
+    @Test
+    public void unexpected_character_after_constraint_value()
+    {
+        try
+        {
+            test("minLength=3 . email");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Unexpected character '.' at position 13 of input string: 
minLength=3 . email");
+        }
     }
 }


Reply via email to