Author: hlship
Date: Wed Feb 24 00:52:43 2010
New Revision: 915625

URL: http://svn.apache.org/viewvc?rev=915625&view=rev
Log:
TAP5-1028: Validator Macros: Combine multiple common validators into a single 
term

Added:
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java
   (with props)
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java
   (with props)
Modified:
    
tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java
    
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
    
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java

Modified: 
tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java?rev=915625&r1=915624&r2=915625&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-annotations/src/main/java/org/apache/tapestry5/beaneditor/Validate.java
 Wed Feb 24 00:52:43 2010
@@ -1,10 +1,10 @@
-// Copyright 2007, 2008, 2009 The Apache Software Foundation
+// Copyright 2007, 2008, 2009, 2010 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
+// 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,
@@ -14,29 +14,34 @@
 
 package org.apache.tapestry5.beaneditor;
 
-import org.apache.tapestry5.ioc.annotations.UseWith;
-import org.apache.tapestry5.ioc.annotations.AnnotationUseContext;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.BEAN;
+import static 
org.apache.tapestry5.ioc.annotations.AnnotationUseContext.COMPONENT;
+import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.MIXIN;
+import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.PAGE;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import java.lang.annotation.Target;
 
-import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.*;
+import org.apache.tapestry5.ioc.annotations.UseWith;
 
 /**
  * Used to attach validation constraints directly to a property (either the 
getter or the setter method). The annotation
  * value is a comma separated list of <em>validation constraints</em>, each 
one identifying a validator type (such as
  * "required", "minlength") and optionally, a constraint value. Most 
validators need a constraint value, which is
- * separated from the type by an equals size (i.e., "maxlength=30").
+ * separated from the type by an equals size (i.e., "maxlength=30"). In 
addition, the value may include
+ * validator macros.
  * <p/>
  * May be placed on any getter or setter method, or on the matching field.
  */
-...@target({ElementType.FIELD, ElementType.METHOD})
+...@target(
+{ ElementType.FIELD, ElementType.METHOD })
 @Retention(RUNTIME)
 @Documented
-...@usewith({BEAN,COMPONENT,MIXIN,PAGE})
+...@usewith(
+{ BEAN, COMPONENT, MIXIN, PAGE })
 public @interface Validate
 {
     String value();

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java?rev=915625&r1=915624&r2=915625&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImpl.java
 Wed Feb 24 00:52:43 2010
@@ -1,10 +1,10 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2010 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
+// 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,
@@ -14,26 +14,30 @@
 
 package org.apache.tapestry5.internal.services;
 
+import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
+import static org.apache.tapestry5.ioc.internal.util.Defense.cast;
+import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.Field;
 import org.apache.tapestry5.FieldValidator;
 import org.apache.tapestry5.Validator;
 import org.apache.tapestry5.ioc.MessageFormatter;
 import org.apache.tapestry5.ioc.Messages;
-import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newList;
-import static org.apache.tapestry5.ioc.internal.util.Defense.cast;
-import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.services.TypeCoercer;
 import org.apache.tapestry5.runtime.Component;
 import org.apache.tapestry5.services.FieldValidatorSource;
 import org.apache.tapestry5.services.FormSupport;
 import org.apache.tapestry5.services.ValidationMessagesSource;
+import org.apache.tapestry5.validator.ValidatorMacro;
 
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
+...@suppresswarnings("unchecked")
 public class FieldValidatorSourceImpl implements FieldValidatorSource
 {
     private final ValidationMessagesSource messagesSource;
@@ -44,13 +48,16 @@
 
     private final FormSupport formSupport;
 
+    private final ValidatorMacro validatorMacro;
+
     public FieldValidatorSourceImpl(ValidationMessagesSource messagesSource, 
TypeCoercer typeCoercer,
-                                    FormSupport formSupport, Map<String, 
Validator> validators)
+            FormSupport formSupport, Map<String, Validator> validators, 
ValidatorMacro validatorMacro)
     {
         this.messagesSource = messagesSource;
         this.typeCoercer = typeCoercer;
         this.formSupport = formSupport;
         this.validators = validators;
+        this.validatorMacro = validatorMacro;
     }
 
     public FieldValidator createValidator(Field field, String validatorType, 
String constraintValue)
@@ -71,15 +78,15 @@
     }
 
     public FieldValidator createValidator(Field field, String validatorType, 
String constraintValue, String overrideId,
-                                          Messages overrideMessages, Locale 
locale)
+            Messages overrideMessages, Locale locale)
     {
         notBlank(validatorType, "validatorType");
 
         Validator validator = validators.get(validatorType);
 
         if (validator == null)
-            throw new IllegalArgumentException(
-                    ServicesMessages.unknownValidatorType(validatorType, 
InternalUtils.sortedKeys(validators)));
+            throw new 
IllegalArgumentException(ServicesMessages.unknownValidatorType(validatorType, 
InternalUtils
+                    .sortedKeys(validators)));
 
         // I just have this thing about always treating parameters as finals, 
so
         // we introduce a second variable to treat a mutable.
@@ -87,66 +94,67 @@
         String formValidationid = formSupport.getFormValidationId();
 
         Object coercedConstraintValue = computeConstraintValue(validatorType, 
validator, constraintValue,
-                                                               
formValidationid,
-                                                               overrideId,
-                                                               
overrideMessages);
+                formValidationid, overrideId, overrideMessages);
 
         MessageFormatter formatter = findMessageFormatter(formValidationid, 
overrideId, overrideMessages, locale,
-                                                          validatorType,
-                                                          validator);
+                validatorType, validator);
 
         return new FieldValidatorImpl(field, coercedConstraintValue, 
formatter, validator, formSupport);
     }
 
     private Object computeConstraintValue(String validatorType, Validator 
validator, String constraintValue,
-                                          String formId, String overrideId,
-                                          Messages overrideMessages)
+            String formId, String overrideId, Messages overrideMessages)
     {
         Class constraintType = validator.getConstraintType();
 
         String constraintText = findConstraintValue(validatorType, 
constraintType, constraintValue, formId, overrideId,
-                                                    overrideMessages);
+                overrideMessages);
 
-        if (constraintText == null) return null;
+        if (constraintText == null)
+            return null;
 
         return typeCoercer.coerce(constraintText, constraintType);
     }
 
     private String findConstraintValue(String validatorType, Class 
constraintType, String constraintValue,
-                                       String formValidationId, String 
overrideId,
-                                       Messages overrideMessages)
+            String formValidationId, String overrideId, Messages 
overrideMessages)
     {
-        if (constraintValue != null) return constraintValue;
+        if (constraintValue != null)
+            return constraintValue;
 
-        if (constraintType == null) return null;
+        if (constraintType == null)
+            return null;
 
         // If no constraint was provided, check to see if it is available via 
a localized message
         // key. This is really handy for complex validations such as patterns.
 
         String perFormKey = formValidationId + "-" + overrideId + "-" + 
validatorType;
 
-        if (overrideMessages.contains(perFormKey)) return 
overrideMessages.get(perFormKey);
+        if (overrideMessages.contains(perFormKey))
+            return overrideMessages.get(perFormKey);
 
         String generalKey = overrideId + "-" + validatorType;
 
-        if (overrideMessages.contains(generalKey)) return 
overrideMessages.get(generalKey);
+        if (overrideMessages.contains(generalKey))
+            return overrideMessages.get(generalKey);
 
-        throw new IllegalArgumentException(
-                ServicesMessages.missingValidatorConstraint(validatorType, 
constraintType, perFormKey, generalKey));
+        throw new 
IllegalArgumentException(ServicesMessages.missingValidatorConstraint(validatorType,
 constraintType,
+                perFormKey, generalKey));
     }
 
     private MessageFormatter findMessageFormatter(String formId, String 
overrideId, Messages overrideMessages,
-                                                  Locale locale,
-                                                  String validatorType, 
Validator validator)
+            Locale locale, String validatorType, Validator validator)
     {
 
         String overrideKey = formId + "-" + overrideId + "-" + validatorType + 
"-message";
 
-        if (overrideMessages.contains(overrideKey)) return 
overrideMessages.getFormatter(overrideKey);
+        if (overrideMessages.contains(overrideKey))
+            return overrideMessages.getFormatter(overrideKey);
 
         overrideKey = overrideId + "-" + validatorType + "-message";
 
-        if (overrideMessages.contains(overrideKey)) return 
overrideMessages.getFormatter(overrideKey);
+        if (overrideMessages.contains(overrideKey))
+            return overrideMessages.getFormatter(overrideKey);
 
         Messages messages = messagesSource.getValidationMessages(locale);
 
@@ -157,21 +165,70 @@
 
     public FieldValidator createValidators(Field field, String specification)
     {
-        List<ValidatorSpecification> specs = parse(specification);
+        List<ValidatorSpecification> specs = 
toValidatorSpecifications(specification);
 
-        List<FieldValidator> fieldValidators = newList();
+        List<FieldValidator> fieldValidators = CollectionFactory.newList();
 
         for (ValidatorSpecification spec : specs)
         {
-            fieldValidators.add(createValidator(field, 
spec.getValidatorType(), spec
-                    .getConstraintValue()));
+            fieldValidators.add(createValidator(field, 
spec.getValidatorType(), spec.getConstraintValue()));
         }
 
-        if (fieldValidators.size() == 1) return fieldValidators.get(0);
+        if (fieldValidators.size() == 1)
+            return fieldValidators.get(0);
 
         return new CompositeFieldValidator(fieldValidators);
     }
 
+    List<ValidatorSpecification> toValidatorSpecifications(String 
specification)
+    {
+        return expandMacros(parse(specification));
+    }
+
+    private List<ValidatorSpecification> 
expandMacros(List<ValidatorSpecification> specs)
+    {
+        Map<String, Boolean> expandedMacros = 
CollectionFactory.newCaseInsensitiveMap();
+        List<ValidatorSpecification> queue = CollectionFactory.newList(specs);
+        List<ValidatorSpecification> result = CollectionFactory.newList();
+
+        while (!queue.isEmpty())
+        {
+            ValidatorSpecification head = queue.remove(0);
+
+            String validatorType = head.getValidatorType();
+
+            String expanded = validatorMacro.valueForMacro(validatorType);
+            if (expanded != null)
+            {
+                if (head.getConstraintValue() != null)
+                    throw new RuntimeException(String.format(
+                            "'%s' is a validator macro, not a validator, and 
can not have a constraint value.",
+                            validatorType));
+
+                if (expandedMacros.containsKey(validatorType))
+                    throw new RuntimeException(String.format("Validator macro 
'%s' appears more than once.",
+                            validatorType));
+
+                expandedMacros.put(validatorType, true);
+
+                List<ValidatorSpecification> parsed = parse(expanded);
+
+                // Add the new validator specifications to the front of the 
queue, replacing the validator macro
+
+                for (int i = 0; i < parsed.size(); i++)
+                {
+                    queue.add(i, parsed.get(i));
+                }
+            }
+            else
+            {
+                result.add(head);
+            }
+        }
+
+        return result;
+    }
+
     /**
      * A code defining what the parser is looking for.
      */
@@ -328,7 +385,7 @@
                 result.add(new ValidatorSpecification(type));
                 break;
 
-                // Case when the specification ends with an equals sign.
+            // Case when the specification ends with an equals sign.
 
             case VALUE_START:
                 result.add(new ValidatorSpecification(type, ""));
@@ -339,7 +396,7 @@
                 result.add(new ValidatorSpecification(type, 
specification.substring(start)));
                 break;
 
-                // For better or worse, ending the string with a comma is 
valid.
+            // For better or worse, ending the string with a comma is valid.
 
             default:
         }

Added: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java?rev=915625&view=auto
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java
 (added)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java
 Wed Feb 24 00:52:43 2010
@@ -0,0 +1,35 @@
+// Copyright 2010 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.tapestry5.internal.validator;
+
+import java.util.Map;
+
+import org.apache.tapestry5.validator.ValidatorMacro;
+
+public class ValidatorMacroImpl implements ValidatorMacro
+{
+    private final Map<String, String> configuration;
+
+    public ValidatorMacroImpl(Map<String, String> configuration)
+    {
+        this.configuration = configuration;
+    }
+
+    public String valueForMacro(String validatorMacro)
+    {
+        return configuration.get(validatorMacro);
+    }
+
+}

Propchange: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/validator/ValidatorMacroImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java?rev=915625&r1=915624&r2=915625&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
 Wed Feb 24 00:52:43 2010
@@ -16,7 +16,6 @@
 
 import java.io.IOException;
 import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.net.URL;
@@ -81,6 +80,7 @@
 import org.apache.tapestry5.internal.util.PrimaryKeyEncoder2ValueEncoder;
 import org.apache.tapestry5.internal.util.RenderableAsBlock;
 import org.apache.tapestry5.internal.util.StringRenderable;
+import org.apache.tapestry5.internal.validator.ValidatorMacroImpl;
 import org.apache.tapestry5.ioc.*;
 import org.apache.tapestry5.ioc.annotations.*;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
@@ -104,6 +104,7 @@
 import org.apache.tapestry5.validator.MinLength;
 import org.apache.tapestry5.validator.Regexp;
 import org.apache.tapestry5.validator.Required;
+import org.apache.tapestry5.validator.ValidatorMacro;
 import org.slf4j.Logger;
 
 /**
@@ -349,6 +350,7 @@
         binder.bind(Dispatcher.class, 
AssetProtectionDispatcher.class).withId("AssetProtectionDispatcher");
         binder.bind(AssetPathAuthorizer.class, 
WhitelistAuthorizer.class).withId("WhitelistAuthorizer");
         binder.bind(AssetPathAuthorizer.class, 
RegexAuthorizer.class).withId("RegexAuthorizer");
+        binder.bind(ValidatorMacro.class, ValidatorMacroImpl.class);
     }
 
     // ========================================================================

Added: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java?rev=915625&view=auto
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java
 (added)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java
 Wed Feb 24 00:52:43 2010
@@ -0,0 +1,35 @@
+// Copyright 2010 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.tapestry5.validator;
+
+import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
+
+/**
+ * Allows support for "validator macros", a simple-minded way of combining 
several related valiations together under
+ * a single name. The service's configuration maps string keys (macro names) 
to string values (validation constraints).
+ * 
+ * @since 5.2.0
+ */
+...@usesmappedconfiguration(String.class)
+public interface ValidatorMacro
+{
+    /**
+     * Given a <em>potential</em> validator macro (a simple string name), 
returns the value for that macro, a
+     * comma-separated list of validation constraints.
+     * 
+     * @return constraints, or null if no such validator macro
+     */
+    String valueForMacro(String validatorMacro);
+}

Propchange: 
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/validator/ValidatorMacro.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java
URL: 
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java?rev=915625&r1=915624&r2=915625&view=diff
==============================================================================
--- 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java
 (original)
+++ 
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/FieldValidatorSourceImplTest.java
 Wed Feb 24 00:52:43 2010
@@ -1,10 +1,10 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2010 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
+// 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,
@@ -22,11 +22,15 @@
 import org.apache.tapestry5.ioc.MessageFormatter;
 import org.apache.tapestry5.ioc.Messages;
 import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newMap;
+
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.services.TypeCoercer;
 import org.apache.tapestry5.runtime.Component;
 import org.apache.tapestry5.services.FieldValidatorSource;
 import org.apache.tapestry5.services.FormSupport;
 import org.apache.tapestry5.services.ValidationMessagesSource;
+import org.apache.tapestry5.validator.ValidatorMacro;
+import org.easymock.EasyMock;
 import org.testng.annotations.Test;
 
 import java.util.Arrays;
@@ -63,7 +67,7 @@
 
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, null, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, null, map, null);
 
         try
         {
@@ -119,7 +123,7 @@
 
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map, null);
 
         FieldValidator fieldValidator = source.createValidator(field, 
"required", null);
 
@@ -164,7 +168,7 @@
 
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map, null);
 
         FieldValidator fieldValidator = source.createValidator(field, 
"required", null);
 
@@ -207,7 +211,7 @@
 
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map, null);
 
         FieldValidator fieldValidator = source.createValidator(field, 
"required", null);
 
@@ -231,7 +235,6 @@
         Messages containerMessages = mockMessages();
         FormSupport fs = mockFormSupport();
 
-
         Map<String, Validator> map = singletonMap("minlength", validator);
 
         train_getConstraintType(validator, Integer.class);
@@ -262,9 +265,12 @@
         train_getValueType(validator, Object.class);
         validator.validate(field, 5, formatter, inputValue);
 
+        ValidatorMacro macro = mockValidatorMacro();
+        train_alwaysNull(macro);
+
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro);
 
         FieldValidator fieldValidator = source.createValidators(field, 
"minlength");
 
@@ -288,7 +294,6 @@
         Messages containerMessages = mockMessages();
         FormSupport fs = mockFormSupport();
 
-
         Map<String, Validator> map = singletonMap("minlength", validator);
 
         train_getConstraintType(validator, Integer.class);
@@ -318,9 +323,12 @@
         train_getValueType(validator, Object.class);
         validator.validate(field, 5, formatter, inputValue);
 
+        ValidatorMacro macro = mockValidatorMacro();
+        train_alwaysNull(macro);
+
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro);
 
         FieldValidator fieldValidator = source.createValidators(field, 
"minlength");
 
@@ -329,7 +337,6 @@
         verify();
     }
 
-
     @SuppressWarnings("unchecked")
     @Test
     public void missing_field_validator_constraint() throws Exception
@@ -356,9 +363,12 @@
         train_contains(containerMessages, "myform-fred-minlength", false);
         train_contains(containerMessages, "fred-minlength", false);
 
+        ValidatorMacro macro = mockValidatorMacro();
+        train_alwaysNull(macro);
+
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro);
 
         try
         {
@@ -367,8 +377,9 @@
         }
         catch (IllegalArgumentException ex)
         {
-            assertEquals(ex.getMessage(),
-                         "Validator 'minlength' requires a validation 
constraint (of type java.lang.Integer) but none was provided. The constraint 
may be provided inside the @Validator annotaton on the property, or in the 
associated component message catalog as key 'myform-fred-minlength' or key 
'fred-minlength'. ");
+            assertEquals(
+                    ex.getMessage(),
+                    "Validator 'minlength' requires a validation constraint 
(of type java.lang.Integer) but none was provided. The constraint may be 
provided inside the @Validator annotaton on the property, or in the associated 
component message catalog as key 'myform-fred-minlength' or key 
'fred-minlength'. ");
         }
 
         verify();
@@ -413,9 +424,12 @@
         train_getValueType(validator, Object.class);
         validator.validate(field, null, formatter, inputValue);
 
+        ValidatorMacro macro = mockValidatorMacro();
+        train_alwaysNull(macro);
+
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro);
 
         FieldValidator fieldValidator = source.createValidators(field, 
"required");
 
@@ -424,6 +438,89 @@
         verify();
     }
 
+    private void train_alwaysNull(ValidatorMacro macro)
+    {
+        
expect(macro.valueForMacro(EasyMock.isA(String.class))).andReturn(null).anyTimes();
+    }
+
+    private ValidatorMacro mockValidatorMacro()
+    {
+        return newMock(ValidatorMacro.class);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void simple_macro_expansion() throws Exception
+    {
+
+        ValidatorMacro macro = mockValidatorMacro();
+        expect(macro.valueForMacro("combo")).andReturn("required,minlength=5");
+        expect(macro.valueForMacro("required")).andReturn(null);
+        expect(macro.valueForMacro("minlength")).andReturn(null);
+
+        replay();
+
+        FieldValidatorSourceImpl source = new FieldValidatorSourceImpl(null, 
null, null, null, macro);
+
+        List<ValidatorSpecification> specs = 
source.toValidatorSpecifications("combo");
+
+        assertListsEquals(specs, new ValidatorSpecification("required"), new 
ValidatorSpecification("minlength", "5"));
+
+        verify();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void macros_can_not_have_constraints() throws Exception
+    {
+
+        ValidatorMacro macro = mockValidatorMacro();
+        expect(macro.valueForMacro("combo")).andReturn("required,minlength=5");
+
+        replay();
+
+        FieldValidatorSourceImpl source = new FieldValidatorSourceImpl(null, 
null, null, null, macro);
+
+        try
+        {
+            source.toValidatorSpecifications("combo=3");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(ex.getMessage(),
+                    "'combo' is a validator macro, not a validator, and can 
not have a constraint value.");
+        }
+
+        verify();
+    }
+
+    @Test
+    public void recursive_macros_are_caught()
+    {
+
+        ValidatorMacro macro = mockValidatorMacro();
+        expect(macro.valueForMacro("combo")).andReturn("required,combo");
+        expect(macro.valueForMacro("required")).andReturn(null);
+        expect(macro.valueForMacro("combo")).andReturn("required,combo");
+
+        replay();
+
+        FieldValidatorSourceImpl source = new FieldValidatorSourceImpl(null, 
null, null, null, macro);
+
+        try
+        {
+            source.toValidatorSpecifications("combo");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(ex.getMessage(), "Validator macro 'combo' appears 
more than once.");
+        }
+
+        verify();
+    }
+
     @SuppressWarnings("unchecked")
     @Test
     public void multiple_validators_via_specification() throws Exception
@@ -481,9 +578,12 @@
         train_getValueType(minLength, String.class);
         minLength.validate(field, fifteen, minLengthFormatter, inputValue);
 
+        ValidatorMacro macro = mockValidatorMacro();
+        train_alwaysNull(macro);
+
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map, macro);
 
         FieldValidator fieldValidator = source.createValidators(field, 
"required,minLength=15");
 
@@ -535,7 +635,7 @@
 
         replay();
 
-        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map);
+        FieldValidatorSource source = new 
FieldValidatorSourceImpl(messagesSource, coercer, fs, map, null);
 
         FieldValidator fieldValidator = source.createValidator(field, 
"minLength", "5");
 
@@ -571,22 +671,22 @@
     @Test
     public void ignore_whitespace_around_type_name()
     {
-        test("  required  ,  email  ", new ValidatorSpecification("required", 
null),
-             new ValidatorSpecification("email", null));
+        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"));
+                new ValidatorSpecification("sameAs", "otherComponentId"));
     }
 
     @Test
     public void whitespace_ignored_around_value()
     {
         test("minLength=  5 , sameAs  = otherComponentId ", new 
ValidatorSpecification("minLength", "5"),
-             new ValidatorSpecification("sameAs", "otherComponentId"));
+                new ValidatorSpecification("sameAs", "otherComponentId"));
     }
 
     @Test
@@ -620,7 +720,7 @@
         catch (RuntimeException ex)
         {
             assertEquals(ex.getMessage(),
-                         "Unexpected character '.' at position 13 of input 
string: minLength=3 . email");
+                    "Unexpected character '.' at position 13 of input string: 
minLength=3 . email");
         }
     }
 }


Reply via email to