You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2006/12/22 00:09:47 UTC

svn commit: r489493 [1/2] - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/corelib/base/ main/java/org/apache/tapestry/internal/ main/java/org/apache/tapestry/internal/bindings/ main/java/org/apache/tapestry/internal/serv...

Author: hlship
Date: Thu Dec 21 15:09:45 2006
New Revision: 489493

URL: http://svn.apache.org/viewvc?view=rev&rev=489493
Log:
Add an additional parameter to BindingSource and BindingFactory to identify the component whose parameter is being bound.
Add validator: binding prefix.
Move toUserPresentable()  to TapestryUtils, move InternalUtilsTest to tapestry-ioc.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactoryTest.java
Removed:
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/util/InternalUtilsTest.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryUtils.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BindingsMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBindingFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/MessageBindingFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/PropBindingFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/BindingSourceImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingSource.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/bindings/BindingsStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/TapestryUtilsTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/BindingFactoryTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/BindingSourceImplTest.java

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/base/AbstractField.java Thu Dec 21 15:09:45 2006
@@ -27,8 +27,8 @@
 import org.apache.tapestry.annotations.SetupRender;
 import org.apache.tapestry.corelib.mixins.RenderDisabled;
 import org.apache.tapestry.corelib.mixins.RenderInformals;
+import org.apache.tapestry.internal.TapestryUtils;
 import org.apache.tapestry.internal.services.FormParameterLookup;
-import org.apache.tapestry.ioc.internal.util.InternalUtils;
 import org.apache.tapestry.services.FormSupport;
 import org.apache.tapestry.services.PageRenderSupport;
 
@@ -164,7 +164,7 @@
         if (_label != null)
             return _label;
 
-        return InternalUtils.toUserPresentable(_resources.getId());
+        return TapestryUtils.toUserPresentable(_resources.getId());
 
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryUtils.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryUtils.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryUtils.java Thu Dec 21 15:09:45 2006
@@ -48,4 +48,38 @@
 
         return Character.toLowerCase(first) + input.substring(1);
     }
+
+    /**
+     * Capitalizes the string, and inserts a space before each upper case character (or sequence of
+     * upper case characters). Thus "userId" becomes "User Id", etc.
+     */
+    public static String toUserPresentable(String id)
+    {
+        StringBuilder builder = new StringBuilder(id.length() * 2);
+
+        char[] chars = id.toCharArray();
+        boolean postSpace = true;
+
+        for (int i = 0; i < chars.length; i++)
+        {
+            char ch = chars[i];
+
+            if (i == 0)
+            {
+                builder.append(Character.toUpperCase(ch));
+                continue;
+            }
+
+            boolean upperCase = Character.isUpperCase(ch);
+
+            if (upperCase && !postSpace)
+                builder.append(' ');
+
+            builder.append(ch);
+
+            postSpace = upperCase;
+        }
+
+        return builder.toString();
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BindingsMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BindingsMessages.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BindingsMessages.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BindingsMessages.java Thu Dec 21 15:09:45 2006
@@ -12,41 +12,47 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.bindings;
-
+package org.apache.tapestry.internal.bindings;
+
+import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.ioc.Messages;
 import org.apache.tapestry.ioc.internal.util.MessagesImpl;
 import org.apache.tapestry.services.Binding;
-
-final class BindingsMessages
-{
-    private static final Messages MESSAGES = MessagesImpl.forClass(BindingsMessages.class);
-
-    private BindingsMessages()
-    {
-    }
-
-    static String bindingIsReadOnly(Binding binding)
-    {
-        return MESSAGES.format("binding-is-read-only", binding);
-    }
-
-    static String bindingIsWriteOnly(Binding binding)
-    {
-        return MESSAGES.format("binding-is-write-only", binding);
-    }
-
-    static String noSuchProperty(Class targetClass, String propertyName, String propertyPath)
-    {
-        return MESSAGES.format(
-                "no-such-property",
-                targetClass.getName(),
-                propertyName,
-                propertyPath);
-    }
-
-    static String writeOnlyProperty(String propertyName, Class clazz, String propertyPath)
-    {
-        return MESSAGES.format("write-only-property", propertyName, clazz.getName(), propertyPath);
-    }
-}
+
+final class BindingsMessages
+{
+    private static final Messages MESSAGES = MessagesImpl.forClass(BindingsMessages.class);
+
+    private BindingsMessages()
+    {
+    }
+
+    static String bindingIsReadOnly(Binding binding)
+    {
+        return MESSAGES.format("binding-is-read-only", binding);
+    }
+
+    static String bindingIsWriteOnly(Binding binding)
+    {
+        return MESSAGES.format("binding-is-write-only", binding);
+    }
+
+    static String noSuchProperty(Class targetClass, String propertyName, String propertyPath)
+    {
+        return MESSAGES.format(
+                "no-such-property",
+                targetClass.getName(),
+                propertyName,
+                propertyPath);
+    }
+
+    static String writeOnlyProperty(String propertyName, Class clazz, String propertyPath)
+    {
+        return MESSAGES.format("write-only-property", propertyName, clazz.getName(), propertyPath);
+    }
+
+    static String validatorBindingForFieldsOnly(ComponentResources component)
+    {
+        return MESSAGES.format("validator-binding-for-fields-only", component.getCompleteId());
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ComponentBindingFactory.java Thu Dec 21 15:09:45 2006
@@ -22,10 +22,10 @@
 /** The "component:" binding prefix, which allows access to a child component via its id. */
 public class ComponentBindingFactory implements BindingFactory
 {
-    public Binding newBinding(String description, ComponentResources component, String expression,
-            Location location)
+    public Binding newBinding(String description, ComponentResources container, ComponentResources component,
+            String expression, Location location)
     {
-        return new ComponentBinding(description, component, expression, location);
+        return new ComponentBinding(description, container, expression, location);
     }
 
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBinding.java Thu Dec 21 15:09:45 2006
@@ -12,41 +12,42 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.bindings;
-
+package org.apache.tapestry.internal.bindings;
+
 import org.apache.tapestry.ioc.Location;
-
-/**
- * Binding type for literal string values, usually supplied in-line in the component template.
- */
-public class LiteralBinding extends AbstractBinding
-{
-    private final String _description;
-
-    private final Object _value;
-
-    public LiteralBinding(String description, Object value, Location location)
-    {
-        super(location);
-        _description = description;
-        _value = value;
-    }
-
-    public Object get()
-    {
-        return _value;
-    }
-
-    @Override
-    public String toString()
-    {
-        return String.format("LiteralBinding[%s: %s]", _description, _value);
-    }
-
-    public Class getBindingType()
-    {
-        // Could be a problem for a LiteralBinding of null but that will certainly be read-only.
-        return _value.getClass();
-    }
-
-}
+
+/**
+ * Binding type for literal, immutable values. This is often used for literal string values supplied
+ * in-line in the component template, but is used for many other things as well.
+ */
+public class LiteralBinding extends AbstractBinding
+{
+    private final String _description;
+
+    private final Object _value;
+
+    public LiteralBinding(String description, Object value, Location location)
+    {
+        super(location);
+        _description = description;
+        _value = value;
+    }
+
+    public Object get()
+    {
+        return _value;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("LiteralBinding[%s: %s]", _description, _value);
+    }
+
+    public Class getBindingType()
+    {
+        // Could be a problem for a LiteralBinding of null but that will certainly be read-only.
+        return _value.getClass();
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBindingFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBindingFactory.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBindingFactory.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/LiteralBindingFactory.java Thu Dec 21 15:09:45 2006
@@ -26,8 +26,8 @@
  */
 public class LiteralBindingFactory implements BindingFactory
 {
-    public Binding newBinding(String description, ComponentResources component, String expression,
-            Location location)
+    public Binding newBinding(String description, ComponentResources container, ComponentResources component,
+            String expression, Location location)
     {
         return new LiteralBinding(description, expression, location);
     }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/MessageBindingFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/MessageBindingFactory.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/MessageBindingFactory.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/MessageBindingFactory.java Thu Dec 21 15:09:45 2006
@@ -26,10 +26,10 @@
 public class MessageBindingFactory implements BindingFactory
 {
 
-    public Binding newBinding(String description, ComponentResources component, String expression,
-            Location location)
+    public Binding newBinding(String description, ComponentResources container, ComponentResources component,
+            String expression, Location location)
     {
-        String messageValue = component.getMessages().get(expression);
+        String messageValue = container.getMessages().get(expression);
 
         return new LiteralBinding(description, messageValue, location);
     }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/PropBindingFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/PropBindingFactory.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/PropBindingFactory.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/PropBindingFactory.java Thu Dec 21 15:09:45 2006
@@ -12,265 +12,265 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.bindings;
-
+package org.apache.tapestry.internal.bindings;
+
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newThreadSafeMap;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Map;
-
-import org.apache.tapestry.ComponentResources;
-import org.apache.tapestry.events.InvalidationListener;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.events.InvalidationListener;
 import org.apache.tapestry.ioc.Location;
 import org.apache.tapestry.ioc.internal.util.TapestryException;
-import org.apache.tapestry.ioc.services.ClassFab;
-import org.apache.tapestry.ioc.services.ClassFabUtils;
-import org.apache.tapestry.ioc.services.ClassFactory;
-import org.apache.tapestry.ioc.services.MethodSignature;
-import org.apache.tapestry.ioc.services.PropertyAccess;
-import org.apache.tapestry.ioc.services.PropertyAdapter;
+import org.apache.tapestry.ioc.services.ClassFab;
+import org.apache.tapestry.ioc.services.ClassFabUtils;
+import org.apache.tapestry.ioc.services.ClassFactory;
+import org.apache.tapestry.ioc.services.MethodSignature;
+import org.apache.tapestry.ioc.services.PropertyAccess;
+import org.apache.tapestry.ioc.services.PropertyAdapter;
 import org.apache.tapestry.ioc.util.BodyBuilder;
 import org.apache.tapestry.services.Binding;
-import org.apache.tapestry.services.BindingFactory;
-
-/**
- * Binding factory for reading and updating JavaBean properties. Uses
- * {@link org.apache.tapestry.ioc.services.PropertyAccess} to analyze the properties, and generates
- * a binding class using the component {@link org.apache.tapestry.ioc.services.ClassFactory}.
- */
-public class PropBindingFactory implements BindingFactory, InvalidationListener
-{
-    private final PropertyAccess _access;
-
-    private final ClassFactory _classFactory;
-
-    // The key is a combination of class name and property path.
-    private final Map<String, BindingConstructor> _cache = newThreadSafeMap();
-
-    private static class BindingConstructor
-    {
-        private final Class _propertyType;
-
-        private final Constructor _constructor;
-
-        BindingConstructor(Class propertyType, Constructor constructor)
-        {
-            _propertyType = propertyType;
-            _constructor = constructor;
-        }
-
-        Binding newBindingInstance(Object target, String toString, Location location)
-                throws Exception
-        {
-            return (Binding) _constructor.newInstance(target, _propertyType, toString, location);
-        }
-    }
-
-    private static final MethodSignature GET_SIGNATURE = new MethodSignature(Object.class, "get",
-            null, null);
-
-    private static final MethodSignature SET_SIGNATURE = new MethodSignature(void.class, "set",
-            new Class[]
-            { Object.class }, null);
-
-    public PropBindingFactory(PropertyAccess propertyAccess, ClassFactory classFactory)
-    {
-        _access = propertyAccess;
-        _classFactory = classFactory;
-    }
-
-    public Binding newBinding(String description, ComponentResources component, String expression,
-            Location location)
-    {
-        Object target = component.getComponent();
-        Class targetClass = target.getClass();
-
-        try
-        {
-            BindingConstructor cons = findCachedConstructor(targetClass, expression);
-
-            String toString = String.format("PropBinding[%s %s(%s)]", description, component
-                    .getCompleteId(), expression);
-
-            return cons.newBindingInstance(target, toString, location);
-        }
-        catch (Throwable ex)
-        {
-            throw new TapestryException(ex.getMessage(), location, ex);
-        }
-    }
-
-    /**
-     * Searches for a cached binding constructor for the given target class and property name. As
-     * necessary, a new cached constructor is created.
-     * 
-     * @param targetClass
-     *            the class of the component which contains the property
-     * @param propertyName
-     *            the name of the property to expose as a binding
-     * @return
-     */
-    private BindingConstructor findCachedConstructor(Class targetClass, String propertyName)
-    {
-        // The only problem with this key is if we can get in a situation where two different
-        // versions of the class (from the default class loader, and the component class loader)
-        // are accessed in this way at the same time. I'm pretty sure that can't happen.
-
-        String key = targetClass.getName() + ":" + propertyName;
-
-        BindingConstructor result = _cache.get(key);
-
-        if (result == null)
-        {
-            result = createConstructor(key, targetClass, propertyName);
-            _cache.put(key, result);
-        }
-
-        return result;
-    }
-
-    private BindingConstructor createConstructor(String key, Class targetClass, String propertyPath)
-    {
-        // Race condition: simulataneous calls to fillCache() for the same targetClass/propertyName
-        // combination may result in duplicate binding classes being created, which causes no great
-        // harm.
-
-        StringBuilder builder = new StringBuilder("_target");
-
-        Class step = targetClass;
-        String[] terms = propertyPath.split("\\.");
-
-        for (int i = 0; i < terms.length - 1; i++)
-        {
-            String name = terms[i];
-
-            PropertyAdapter pa = _access.getAdapter(step).getPropertyAdapter(name);
-
-            if (pa == null)
-                throw new RuntimeException(BindingsMessages
-                        .noSuchProperty(step, name, propertyPath));
-
-            Method m = pa.getReadMethod();
-
-            if (m == null)
-                throw new RuntimeException(BindingsMessages.writeOnlyProperty(
-                        name,
-                        step,
-                        propertyPath));
-
-            // Extend the expression by invoking the read access method.
-
-            builder.append(".");
-            builder.append(m.getName());
-            builder.append("()");
-
-            // The next property (in the path, or the terminal) that is evaluated is in terms of the
-            // return type of this method.
-
-            step = pa.getType();
-        }
-
-        String terminalName = terms[terms.length - 1];
-
-        PropertyAdapter adapter = _access.getAdapter(step).getPropertyAdapter(terminalName);
-
-        if (adapter == null)
-            throw new RuntimeException(BindingsMessages.noSuchProperty(
-                    targetClass,
-                    terminalName,
-                    propertyPath));
-
-        Class bindingClass = createBindingClass(
-                targetClass,
-                builder.toString(),
-                terminalName,
-                adapter);
-
-        // The fabricated class is only going to have the one constructor. This is the easiest
-        // way to access it.
-
-        BindingConstructor result = new BindingConstructor(adapter.getType(), bindingClass
-                .getConstructors()[0]);
-
-        return result;
-    }
-
-    /**
-     * @param targetClass
-     *            root class of expression
-     * @param pathExpression
-     *            Javassist expression to navigate to the terminal property, starting with the
-     *            _target instance variable
-     * @param propertyName
-     *            the name of the terminal property
-     * @param adapter
-     *            property adapter for the terminal property
-     * @return Class that implements Binding
-     */
-    private Class createBindingClass(Class targetClass, String pathExpression, String propertyName,
-            PropertyAdapter adapter)
-    {
-        String name = ClassFabUtils.generateClassName("PropBinding");
-
-        ClassFab classFab = _classFactory.newClass(name, BasePropBinding.class);
-
-        classFab.addField("_target", targetClass);
-
-        classFab.addConstructor(
-                new Class[]
-                { targetClass, Class.class, String.class, Location.class },
-                null,
-                "{ super($2, $3, $4); _target = $1; }");
-
-        if (adapter.isRead())
-        {
-            String body = String.format("return ($w) %s.%s();", pathExpression, adapter
-                    .getReadMethod().getName());
-
-            classFab.addMethod(Modifier.PUBLIC, GET_SIGNATURE, body);
-        }
-
-        if (adapter.isUpdate())
-        {
-            Class propertyType = adapter.getType();
-
-            BodyBuilder builder = new BodyBuilder();
-            builder.begin();
-
-            String propertyTypeName = propertyType.getName();
-            builder.add("%s value = ", propertyTypeName);
-
-            if (propertyType.isPrimitive())
-            {
-                String wrapperType = ClassFabUtils.getWrapperTypeName(propertyTypeName);
-                String unwrapMethod = ClassFabUtils.getUnwrapMethodName(propertyTypeName);
-
-                // Cast the value to the wrapper type, and then extract the primitive
-                // value from that.
-
-                builder.addln("((%s) $1).%s();", wrapperType, unwrapMethod);
-            }
-            else
-                builder.addln("(%s) $1;", propertyTypeName);
-
-            builder.addln("%s.%s(value);", pathExpression, adapter.getWriteMethod().getName());
-            builder.end();
-
-            classFab.addMethod(Modifier.PUBLIC, SET_SIGNATURE, builder.toString());
-        }
-
-        return classFab.createClass();
-    }
-
-    /**
-     * The cache contains references to classes loaded by the Tapestry class loader. When that
-     * loader is invalidated (due to class file changes), the cache is cleared and rebuilt.
-     */
-    public void objectWasInvalidated()
-    {
-        _cache.clear();
-    }
-
-}
+import org.apache.tapestry.services.BindingFactory;
+
+/**
+ * Binding factory for reading and updating JavaBean properties. Uses
+ * {@link org.apache.tapestry.ioc.services.PropertyAccess} to analyze the properties, and generates
+ * a binding class using the component {@link org.apache.tapestry.ioc.services.ClassFactory}.
+ */
+public class PropBindingFactory implements BindingFactory, InvalidationListener
+{
+    private final PropertyAccess _access;
+
+    private final ClassFactory _classFactory;
+
+    // The key is a combination of class name and property path.
+    private final Map<String, BindingConstructor> _cache = newThreadSafeMap();
+
+    private static class BindingConstructor
+    {
+        private final Class _propertyType;
+
+        private final Constructor _constructor;
+
+        BindingConstructor(Class propertyType, Constructor constructor)
+        {
+            _propertyType = propertyType;
+            _constructor = constructor;
+        }
+
+        Binding newBindingInstance(Object target, String toString, Location location)
+                throws Exception
+        {
+            return (Binding) _constructor.newInstance(target, _propertyType, toString, location);
+        }
+    }
+
+    private static final MethodSignature GET_SIGNATURE = new MethodSignature(Object.class, "get",
+            null, null);
+
+    private static final MethodSignature SET_SIGNATURE = new MethodSignature(void.class, "set",
+            new Class[]
+            { Object.class }, null);
+
+    public PropBindingFactory(PropertyAccess propertyAccess, ClassFactory classFactory)
+    {
+        _access = propertyAccess;
+        _classFactory = classFactory;
+    }
+
+    public Binding newBinding(String description, ComponentResources container,
+            ComponentResources component, String expression, Location location)
+    {
+        Object target = container.getComponent();
+        Class targetClass = target.getClass();
+
+        try
+        {
+            BindingConstructor cons = findCachedConstructor(targetClass, expression);
+
+            String toString = String.format("PropBinding[%s %s(%s)]", description, container
+                    .getCompleteId(), expression);
+
+            return cons.newBindingInstance(target, toString, location);
+        }
+        catch (Throwable ex)
+        {
+            throw new TapestryException(ex.getMessage(), location, ex);
+        }
+    }
+
+    /**
+     * Searches for a cached binding constructor for the given target class and property name. As
+     * necessary, a new cached constructor is created.
+     * 
+     * @param targetClass
+     *            the class of the component which contains the property
+     * @param propertyName
+     *            the name of the property to expose as a binding
+     * @return
+     */
+    private BindingConstructor findCachedConstructor(Class targetClass, String propertyName)
+    {
+        // The only problem with this key is if we can get in a situation where two different
+        // versions of the class (from the default class loader, and the component class loader)
+        // are accessed in this way at the same time. I'm pretty sure that can't happen.
+
+        String key = targetClass.getName() + ":" + propertyName;
+
+        BindingConstructor result = _cache.get(key);
+
+        if (result == null)
+        {
+            result = createConstructor(key, targetClass, propertyName);
+            _cache.put(key, result);
+        }
+
+        return result;
+    }
+
+    private BindingConstructor createConstructor(String key, Class targetClass, String propertyPath)
+    {
+        // Race condition: simulataneous calls to fillCache() for the same targetClass/propertyName
+        // combination may result in duplicate binding classes being created, which causes no great
+        // harm.
+
+        StringBuilder builder = new StringBuilder("_target");
+
+        Class step = targetClass;
+        String[] terms = propertyPath.split("\\.");
+
+        for (int i = 0; i < terms.length - 1; i++)
+        {
+            String name = terms[i];
+
+            PropertyAdapter pa = _access.getAdapter(step).getPropertyAdapter(name);
+
+            if (pa == null)
+                throw new RuntimeException(BindingsMessages
+                        .noSuchProperty(step, name, propertyPath));
+
+            Method m = pa.getReadMethod();
+
+            if (m == null)
+                throw new RuntimeException(BindingsMessages.writeOnlyProperty(
+                        name,
+                        step,
+                        propertyPath));
+
+            // Extend the expression by invoking the read access method.
+
+            builder.append(".");
+            builder.append(m.getName());
+            builder.append("()");
+
+            // The next property (in the path, or the terminal) that is evaluated is in terms of the
+            // return type of this method.
+
+            step = pa.getType();
+        }
+
+        String terminalName = terms[terms.length - 1];
+
+        PropertyAdapter adapter = _access.getAdapter(step).getPropertyAdapter(terminalName);
+
+        if (adapter == null)
+            throw new RuntimeException(BindingsMessages.noSuchProperty(
+                    targetClass,
+                    terminalName,
+                    propertyPath));
+
+        Class bindingClass = createBindingClass(
+                targetClass,
+                builder.toString(),
+                terminalName,
+                adapter);
+
+        // The fabricated class is only going to have the one constructor. This is the easiest
+        // way to access it.
+
+        BindingConstructor result = new BindingConstructor(adapter.getType(), bindingClass
+                .getConstructors()[0]);
+
+        return result;
+    }
+
+    /**
+     * @param targetClass
+     *            root class of expression
+     * @param pathExpression
+     *            Javassist expression to navigate to the terminal property, starting with the
+     *            _target instance variable
+     * @param propertyName
+     *            the name of the terminal property
+     * @param adapter
+     *            property adapter for the terminal property
+     * @return Class that implements Binding
+     */
+    private Class createBindingClass(Class targetClass, String pathExpression, String propertyName,
+            PropertyAdapter adapter)
+    {
+        String name = ClassFabUtils.generateClassName("PropBinding");
+
+        ClassFab classFab = _classFactory.newClass(name, BasePropBinding.class);
+
+        classFab.addField("_target", targetClass);
+
+        classFab.addConstructor(
+                new Class[]
+                { targetClass, Class.class, String.class, Location.class },
+                null,
+                "{ super($2, $3, $4); _target = $1; }");
+
+        if (adapter.isRead())
+        {
+            String body = String.format("return ($w) %s.%s();", pathExpression, adapter
+                    .getReadMethod().getName());
+
+            classFab.addMethod(Modifier.PUBLIC, GET_SIGNATURE, body);
+        }
+
+        if (adapter.isUpdate())
+        {
+            Class propertyType = adapter.getType();
+
+            BodyBuilder builder = new BodyBuilder();
+            builder.begin();
+
+            String propertyTypeName = propertyType.getName();
+            builder.add("%s value = ", propertyTypeName);
+
+            if (propertyType.isPrimitive())
+            {
+                String wrapperType = ClassFabUtils.getWrapperTypeName(propertyTypeName);
+                String unwrapMethod = ClassFabUtils.getUnwrapMethodName(propertyTypeName);
+
+                // Cast the value to the wrapper type, and then extract the primitive
+                // value from that.
+
+                builder.addln("((%s) $1).%s();", wrapperType, unwrapMethod);
+            }
+            else
+                builder.addln("(%s) $1;", propertyTypeName);
+
+            builder.addln("%s.%s(value);", pathExpression, adapter.getWriteMethod().getName());
+            builder.end();
+
+            classFab.addMethod(Modifier.PUBLIC, SET_SIGNATURE, builder.toString());
+        }
+
+        return classFab.createClass();
+    }
+
+    /**
+     * The cache contains references to classes loaded by the Tapestry class loader. When that
+     * loader is invalidated (due to class file changes), the cache is cleared and rebuilt.
+     */
+    public void objectWasInvalidated()
+    {
+        _cache.clear();
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactory.java?view=auto&rev=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactory.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactory.java Thu Dec 21 15:09:45 2006
@@ -0,0 +1,60 @@
+// Copyright 2006 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.internal.bindings;
+
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.Field;
+import org.apache.tapestry.FieldValidator;
+import org.apache.tapestry.ioc.Location;
+import org.apache.tapestry.ioc.internal.util.TapestryException;
+import org.apache.tapestry.ioc.services.FieldValidatorSource;
+import org.apache.tapestry.services.Binding;
+import org.apache.tapestry.services.BindingFactory;
+
+/**
+ * Factory for bindings that provide a {@link FieldValidator} based on a validator specification.
+ * This binding factory is only useable with components that implement the {@link Field} interface.
+ */
+public class ValidatorBindingFactory implements BindingFactory
+{
+    private final FieldValidatorSource _fieldValidatorSource;
+
+    public ValidatorBindingFactory(FieldValidatorSource fieldValidatorSource)
+    {
+        _fieldValidatorSource = fieldValidatorSource;
+    }
+
+    public Binding newBinding(String description, ComponentResources container,
+            ComponentResources component, String expression, Location location)
+    {
+        Object fieldAsObject = component.getComponent();
+
+        if (!Field.class.isInstance(fieldAsObject))
+            throw new TapestryException(BindingsMessages.validatorBindingForFieldsOnly(component),
+                    location, null);
+
+        Field field = (Field) fieldAsObject;
+
+        // The expression is a validator specification, such as "required,minLength=5".
+        // ValidatorBindingFactory is the odd man out becasuse it needs the binding component (the
+        // component whose parameter is to be bound) rather than the containing component, the way
+        // most factories work.
+
+        FieldValidator validator = _fieldValidatorSource.createValidators(field, expression);
+
+        return new LiteralBinding(description, validator, location);
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/BindingSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/BindingSourceImpl.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/BindingSourceImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/BindingSourceImpl.java Thu Dec 21 15:09:45 2006
@@ -12,64 +12,67 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.services;
-
-import java.util.Map;
-
-import org.apache.tapestry.ComponentResources;
+package org.apache.tapestry.internal.services;
+
+import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
+import static org.apache.tapestry.ioc.internal.util.Defense.notNull;
+
+import java.util.Map;
+
+import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.ioc.Location;
-import org.apache.tapestry.ioc.internal.util.Defense;
 import org.apache.tapestry.ioc.internal.util.TapestryException;
 import org.apache.tapestry.services.Binding;
-import org.apache.tapestry.services.BindingFactory;
-import org.apache.tapestry.services.BindingSource;
-
-public class BindingSourceImpl implements BindingSource
-{
-    private final Map<String, BindingFactory> _factories;
-
-    public BindingSourceImpl(Map<String, BindingFactory> factories)
-    {
-        _factories = factories;
-    }
-
-    public Binding newBinding(String description, ComponentResources component,
-            String defaultPrefix, String expression, Location location)
-    {
-        Defense.notBlank(description, "description");
-        Defense.notNull(component, "component");
-        Defense.notBlank(defaultPrefix, "defaultPrefix");
-        Defense.notBlank(expression, "expression");
-        // Location might be null
-
-        String subexpression = expression;
-        int colonx = expression.indexOf(':');
-
-        BindingFactory factory = null;
-
-        if (colonx > 0)
-        {
-            String prefix = expression.substring(0, colonx);
-
-            factory = _factories.get(prefix);
-            if (factory != null)
-                subexpression = expression.substring(colonx + 1);
-        }
-
-        if (factory == null)
-            factory = _factories.get(defaultPrefix);
-
-        // And if that's null, what then? We assume that the default prefix is a valid prefix,
-        // or we'll get an NPE below and report it like any other error.
-
-        try
-        {
-            return factory.newBinding(description, component, subexpression, location);
-        }
-        catch (Exception ex)
-        {
-            throw new TapestryException(ServicesMessages.bindingSourceFailure(expression, ex),
-                    location, ex);
-        }
-    }
-}
+import org.apache.tapestry.services.BindingFactory;
+import org.apache.tapestry.services.BindingSource;
+
+public class BindingSourceImpl implements BindingSource
+{
+    private final Map<String, BindingFactory> _factories;
+
+    public BindingSourceImpl(Map<String, BindingFactory> factories)
+    {
+        _factories = factories;
+    }
+
+    public Binding newBinding(String description, ComponentResources container,
+            ComponentResources component, String defaultPrefix, String expression, Location location)
+    {
+        notBlank(description, "description");
+        notNull(container, "container");
+        notNull(component, "component");
+        notBlank(defaultPrefix, "defaultPrefix");
+        notBlank(expression, "expression");
+        // Location might be null
+
+        String subexpression = expression;
+        int colonx = expression.indexOf(':');
+
+        BindingFactory factory = null;
+
+        if (colonx > 0)
+        {
+            String prefix = expression.substring(0, colonx);
+
+            factory = _factories.get(prefix);
+            if (factory != null)
+                subexpression = expression.substring(colonx + 1);
+        }
+
+        if (factory == null)
+            factory = _factories.get(defaultPrefix);
+
+        // And if that's null, what then? We assume that the default prefix is a valid prefix,
+        // or we'll get an NPE below and report it like any other error.
+
+        try
+        {
+            return factory.newBinding(description, container, component, subexpression, location);
+        }
+        catch (Exception ex)
+        {
+            throw new TapestryException(ServicesMessages.bindingSourceFailure(expression, ex),
+                    location, ex);
+        }
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalModule.java Thu Dec 21 15:09:45 2006
@@ -376,8 +376,8 @@
                 _keywords.put("null", null);
             }
 
-            public Binding newBinding(String description, ComponentResources component,
-                    String expression, Location location)
+            public Binding newBinding(String description, ComponentResources container,
+                    ComponentResources component, String expression, Location location)
             {
                 String key = expression.trim().toLowerCase();
 
@@ -391,11 +391,11 @@
         BindingFactory thisFactory = new BindingFactory()
         {
 
-            public Binding newBinding(String description, ComponentResources component,
-                    String expression, Location location)
+            public Binding newBinding(String description, ComponentResources container,
+                    ComponentResources component, String expression, Location location)
             {
                 if ("this".equalsIgnoreCase(expression.trim()))
-                    return new LiteralBinding(description, component.getComponent(), location);
+                    return new LiteralBinding(description, container.getComponent(), location);
 
                 return null;
             }
@@ -405,8 +405,8 @@
         {
             private final Pattern _pattern = Pattern.compile("^\\s*(-?\\d+)\\s*$");
 
-            public Binding newBinding(String description, ComponentResources component,
-                    String expression, Location location)
+            public Binding newBinding(String description, ComponentResources container,
+                    ComponentResources component, String expression, Location location)
             {
                 Matcher matcher = _pattern.matcher(expression);
 
@@ -426,8 +426,8 @@
             private final Pattern _pattern = Pattern
                     .compile("^\\s*(-?\\d+)\\s*\\.\\.\\s*(-?\\d+)\\s*$");
 
-            public Binding newBinding(String description, ComponentResources component,
-                    String expression, Location location)
+            public Binding newBinding(String description, ComponentResources container,
+                    ComponentResources component, String expression, Location location)
             {
                 Matcher matcher = _pattern.matcher(expression);
 
@@ -451,8 +451,8 @@
             private final Pattern _pattern = Pattern
                     .compile("^\\s*(\\-?((\\d+\\.)|(\\d*\\.\\d+)))\\s*$");
 
-            public Binding newBinding(String description, ComponentResources component,
-                    String expression, Location location)
+            public Binding newBinding(String description, ComponentResources container,
+                    ComponentResources component, String expression, Location location)
             {
                 Matcher matcher = _pattern.matcher(expression);
 
@@ -473,8 +473,8 @@
 
             private final Pattern _pattern = Pattern.compile("^\\s*'(.*)'\\s*$");
 
-            public Binding newBinding(String description, ComponentResources component,
-                    String expression, Location location)
+            public Binding newBinding(String description, ComponentResources container,
+                    ComponentResources component, String expression, Location location)
             {
                 Matcher matcher = _pattern.matcher(expression);
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactoryImpl.java Thu Dec 21 15:09:45 2006
@@ -109,9 +109,9 @@
         Binding binding = _bindingSource.newBinding(
                 "expansion",
                 componentResources,
+                componentResources,
                 InternalConstants.PROP_BINDING_PREFIX,
-                token.getExpression(),
-                token.getLocation());
+                token.getExpression(), token.getLocation());
 
         return new ExpansionPageElement(binding, _typeCoercer);
     }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderImpl.java Thu Dec 21 15:09:45 2006
@@ -385,8 +385,13 @@
             // At some point we may add meta data to control what the default prefix is within a
             // component.
 
-            Binding binding = _bindingSource.newBinding("parameter " + name, loadingComponent
-                    .getComponentResources(), InternalConstants.PROP_BINDING_PREFIX, value, null);
+            Binding binding = _bindingSource.newBinding(
+                    "parameter " + name,
+                    loadingComponent.getComponentResources(),
+                    component.getComponentResources(),
+                    InternalConstants.PROP_BINDING_PREFIX,
+                    value,
+                    null);
 
             component.addParameter(name, binding);
         }
@@ -427,6 +432,7 @@
         Binding binding = _bindingSource.newBinding(
                 "parameter " + name,
                 loadingElement.getComponentResources(),
+                component.getComponentResources(),
                 InternalConstants.PROP_BINDING_PREFIX,
                 token.getValue(),
                 token.getLocation());

Modified: 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=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ValidatorSpecification.java Thu Dec 21 15:09:45 2006
@@ -1,3 +1,17 @@
+// Copyright 2006 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.internal.services;
 
 /** Validator type and constraint values parsed from a validator specification. */

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/test/InternalBaseTestCase.java Thu Dec 21 15:09:45 2006
@@ -53,8 +53,8 @@
 import org.apache.tapestry.services.BindingSource;
 import org.apache.tapestry.services.ComponentClassResolver;
 import org.apache.tapestry.services.Infrastructure;
-import org.apache.tapestry.services.TapestryModule;
 import org.apache.tapestry.services.Request;
+import org.apache.tapestry.services.TapestryModule;
 import org.apache.tapestry.test.TapestryTestCase;
 import org.easymock.EasyMock;
 import org.testng.annotations.AfterMethod;
@@ -134,11 +134,6 @@
     protected final ComponentInstantiatorSource newComponentInstantiatorSource()
     {
         return newMock(ComponentInstantiatorSource.class);
-    }
-
-    protected Component newComponent()
-    {
-        return newMock(Component.class);
     }
 
     protected final Page newPage()

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingFactory.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingFactory.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingFactory.java Thu Dec 21 15:09:45 2006
@@ -12,31 +12,37 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.services;
-
-import org.apache.tapestry.ComponentResources;
+package org.apache.tapestry.services;
+
+import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.ioc.Location;
-
-/**
- * Creates a binding of a particular type.
- * 
- * 
- */
-public interface BindingFactory
-{
-    /**
-     * Creates a new binding instance.
-     * 
-     * @param description
-     *            of the binding, such as, "parameter foo"
-     * @param component
-     *            the component, as represented by its resources, for which a binding is to be
-     *            created.
-     * @param expression
-     * @param location
-     *            from which the binding was generate, or null if not known
-     * @return the new binding instance
-     */
-    Binding newBinding(String description, ComponentResources component, String expression,
-            Location location);
-}
+
+/**
+ * Creates a binding of a particular type.
+ */
+public interface BindingFactory
+{
+    /**
+     * Creates a new binding instance.
+     * <p>
+     * The binding represents a connection between the container and the component (the component is
+     * usually the child of the component, though in a few cases, it is the component itself). In
+     * most cases, the expression is evaluated in terms of the resources of the <em>container</em>
+     * and the component is ignored.
+     * 
+     * @param description
+     *            of the binding, such as, "parameter foo"
+     * @param container
+     *            the component, as represented by its resources, for which a binding is to be
+     *            created.
+     * @param component
+     *            the component whose parameter is to be bound by the resulting binding (rarely
+     *            used)
+     * @param expression
+     * @param location
+     *            from which the binding was generate, or null if not known
+     * @return the new binding instance
+     */
+    Binding newBinding(String description, ComponentResources container,
+            ComponentResources component, String expression, Location location);
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingSource.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingSource.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/BindingSource.java Thu Dec 21 15:09:45 2006
@@ -12,35 +12,41 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.services;
-
-import org.apache.tapestry.ComponentResources;
+package org.apache.tapestry.services;
+
+import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.ioc.Location;
-
-/**
- * Used to acquire bindings for component parameters.
- * 
- * 
- */
-public interface BindingSource
-{
-    /**
-     * Examines the expression and strips off the leading prefix. The prefix is used to choose the
-     * appropriate {@link BindingFactory}, which recieves the description, the expression (after
-     * the prefix), and the location. If the prefix doesn't exist, or if there's no prefix, then the
-     * factory for the default prefix (often "literal") is used (and passed the full prefix).
-     * 
-     * @param description
-     *            description of the binding, such as "parameter foo"
-     * @param component
-     * @param defaultPrefix
-     *            the default prefix used when the expression itself does not have a prefix
-     * @param expression
-     *            the binding
-     * @param location
-     *            location assigned to the binding (or null if not known)
-     * @return a binding
-     */
-    Binding newBinding(String description, ComponentResources component, String defaultPrefix,
-            String expression, Location location);
-}
+
+/**
+ * Used to acquire bindings for component parameters.
+ */
+public interface BindingSource
+{
+    /**
+     * Examines the expression and strips off the leading prefix. The prefix is used to choose the
+     * appropriate {@link BindingFactory}, which recieves the description, the expression (after
+     * the prefix), and the location. If the prefix doesn't exist, or if there's no prefix, then the
+     * factory for the default prefix (often "literal") is used (and passed the full prefix).
+     * <p>
+     * The binding represents a connection between the container and the component (the component is
+     * usually the child of the component, though in a few cases, it is the component itself). In
+     * most cases, the expression is evaluated in terms of the resources of the <em>container</em>
+     * and the component is ignored.
+     * 
+     * @param description
+     *            description of the binding, such as "parameter foo"
+     * @param container
+     *            typically, the parent of the component
+     * @param component
+     *            the component whose paramter is to be bound
+     * @param defaultPrefix
+     *            the default prefix used when the expression itself does not have a prefix
+     * @param expression
+     *            the binding
+     * @param location
+     *            location assigned to the binding (or null if not known)
+     * @return a binding
+     */
+    Binding newBinding(String description, ComponentResources container,
+            ComponentResources component, String defaultPrefix, String expression, Location location);
+}

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=489493&r1=489492&r2=489493
==============================================================================
--- 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 15:09:45 2006
@@ -42,6 +42,7 @@
 import org.apache.tapestry.internal.bindings.ComponentBindingFactory;
 import org.apache.tapestry.internal.bindings.LiteralBindingFactory;
 import org.apache.tapestry.internal.bindings.MessageBindingFactory;
+import org.apache.tapestry.internal.bindings.ValidatorBindingFactory;
 import org.apache.tapestry.internal.services.ActionLinkHandler;
 import org.apache.tapestry.internal.services.ActionLinkHandlerImpl;
 import org.apache.tapestry.internal.services.ApplicationGlobalsImpl;
@@ -131,8 +132,7 @@
  * The root module for Tapestry.
  */
 @Id("tapestry")
-@SubModule(
-{ InternalModule.class })
+@SubModule(InternalModule.class)
 public final class TapestryModule
 {
     private final ChainBuilder _chainBuilder;
@@ -516,17 +516,19 @@
 
     /**
      * Contributes the factory for serveral built-in binding prefixes ("literal", prop", "component"
-     * and "message").
+     * "message" and "validator").
      */
     public static void contributeBindingSource(
             MappedConfiguration<String, BindingFactory> configuration,
             @InjectService("tapestry.internal.PropBindingFactory")
-            BindingFactory propBindingFactory)
+            BindingFactory propBindingFactory, @Inject("infrastructure:FieldValidatorSource")
+            FieldValidatorSource fieldValidatorSource)
     {
         configuration.add(InternalConstants.LITERAL_BINDING_PREFIX, new LiteralBindingFactory());
         configuration.add(InternalConstants.PROP_BINDING_PREFIX, propBindingFactory);
         configuration.add("component", new ComponentBindingFactory());
         configuration.add("message", new MessageBindingFactory());
+        configuration.add("validator", new ValidatorBindingFactory(fieldValidatorSource));
     }
 
     /**

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java Thu Dec 21 15:09:45 2006
@@ -38,6 +38,7 @@
 import org.apache.tapestry.Asset;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.ComponentResourcesCommon;
+import org.apache.tapestry.FieldValidator;
 import org.apache.tapestry.MarkupWriter;
 import org.apache.tapestry.Validator;
 import org.apache.tapestry.annotations.Inject;
@@ -47,6 +48,7 @@
 import org.apache.tapestry.ioc.Resource;
 import org.apache.tapestry.ioc.ServiceLocator;
 import org.apache.tapestry.ioc.internal.util.ClasspathResource;
+import org.apache.tapestry.ioc.services.FieldValidatorSource;
 import org.apache.tapestry.ioc.services.ThreadLocale;
 import org.apache.tapestry.ioc.test.IOCTestCase;
 import org.apache.tapestry.model.ComponentModel;
@@ -181,9 +183,11 @@
     }
 
     protected final void train_newBinding(BindingFactory factory, String description,
-            ComponentResources component, String expression, Location l, Binding b)
+            ComponentResources container, ComponentResources component, String expression,
+            Location l, Binding binding)
     {
-        expect(factory.newBinding(description, component, expression, l)).andReturn(b);
+        expect(factory.newBinding(description, container, component, expression, l)).andReturn(
+                binding);
     }
 
     protected final ComponentResources newComponentResources()
@@ -401,8 +405,8 @@
         return newMock(RequestHandler.class);
     }
 
-    protected final void train_service(RequestHandler handler, Request request,
-            Response response, boolean result) throws IOException
+    protected final void train_service(RequestHandler handler, Request request, Response response,
+            boolean result) throws IOException
     {
         expect(handler.service(request, response)).andReturn(result);
     }
@@ -465,7 +469,8 @@
     protected final <T extends Annotation> void train_getMethodAnnotation(ClassTransformation ct,
             MethodSignature signature, Class<T> annotationClass, T annotation)
     {
-        expect(ct.getMethodAnnotation(signature, annotationClass)).andReturn(annotation).atLeastOnce();
+        expect(ct.getMethodAnnotation(signature, annotationClass)).andReturn(annotation)
+                .atLeastOnce();
     }
 
     protected final ClasspathAssetAliasManager newClasspathAssetAliasManager()
@@ -511,7 +516,8 @@
         expect(request.getPath()).andReturn(path).atLeastOnce();
     }
 
-    protected final void train_toResourcePath(ClasspathAssetAliasManager manager, String clientURL, String resourcePath)
+    protected final void train_toResourcePath(ClasspathAssetAliasManager manager, String clientURL,
+            String resourcePath)
     {
         expect(manager.toResourcePath(clientURL)).andReturn(resourcePath).atLeastOnce();
     }
@@ -521,7 +527,8 @@
         expect(request.getDateHeader(name)).andReturn(value).atLeastOnce();
     }
 
-    protected final void train_findMethods(ClassTransformation transformation, final MethodSignature... signatures)
+    protected final void train_findMethods(ClassTransformation transformation,
+            final MethodSignature... signatures)
     {
         IAnswer<List<MethodSignature>> answer = new IAnswer<List<MethodSignature>>()
         {
@@ -529,24 +536,24 @@
             {
                 // Can't think of a way to do this without duplicating some code out of
                 // InternalClassTransformationImpl
-    
+
                 List<MethodSignature> result = newList();
                 MethodFilter filter = (MethodFilter) EasyMock.getCurrentArguments()[0];
-    
+
                 for (MethodSignature sig : signatures)
                 {
                     if (filter.accept(sig))
                         result.add(sig);
                 }
-    
+
                 // We don't have to sort them for testing purposes. Usually there's just going to be
                 // one in there.
-    
+
                 return result;
             }
-    
+
         };
-    
+
         expect(transformation.findMethods(EasyMock.isA(MethodFilter.class))).andAnswer(answer);
     }
 
@@ -555,7 +562,8 @@
         expect(model.isRootClass()).andReturn(isRootClass);
     }
 
-    protected final void train_getValidationMessages(ValidationMessagesSource messagesSource, Locale locale, Messages messages)
+    protected final void train_getValidationMessages(ValidationMessagesSource messagesSource,
+            Locale locale, Messages messages)
     {
         expect(messagesSource.getValidationMessages(locale)).andReturn(messages).atLeastOnce();
     }
@@ -578,5 +586,25 @@
     protected final ValidationMessagesSource newValidationMessagesSource()
     {
         return newMock(ValidationMessagesSource.class);
+    }
+
+    protected final FieldValidator newFieldValidator()
+    {
+        return newMock(FieldValidator.class);
+    }
+
+    protected FieldValidatorSource newFieldValidatorSource()
+    {
+        return newMock(FieldValidatorSource.class);
+    }
+
+    protected final Component newComponent()
+    {
+        return newMock(Component.class);
+    }
+
+    protected final void train_getComponent(ComponentResources resources, Component component)
+    {
+        expect(resources.getComponent()).andReturn(component).atLeastOnce();
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/bindings/BindingsStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/bindings/BindingsStrings.properties?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/bindings/BindingsStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/bindings/BindingsStrings.properties Thu Dec 21 15:09:45 2006
@@ -16,3 +16,4 @@
 binding-is-write-only=Binding %s is write-only.
 no-such-property=Class %s does not contain a property named '%s' (within property path '%s').
 write-only-property=Property '%s' of class %s (within property path '%s') is not readable (it has no read accessor method).
+validator-binding-for-fields-only=Component '%s' is not a field (it does not implement the Field interface) and may not be used with the validator: binding prefix.

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/parameters.apt Thu Dec 21 15:09:45 2006
@@ -125,7 +125,8 @@
 *------------+----------------------------------------------------------------------------------+
 | prop:      | The name of a property of the containing component to read or update.            |
 *------------+----------------------------------------------------------------------------------+
-
+| validator: | A <validator specification> used to create some number of field validators.      |
+*------------+----------------------------------------------------------------------------------+
 
   The default binding prefix is "prop:".
 
@@ -162,6 +163,24 @@
   
   Such values are read only and invariant.
   
+Validator Bindings
+
+  The "validator:" binding prefix is highly specialized. It allows a short string to be
+  used to create and configure the objects that perform input validation for 
+  form control components, such as TextField and Checkbox.
+  
+  The string is a comma-separated list of   <validator types>.  These are short aliases
+  that for objects that perform the validation.  In many cases, the validation is configurable
+  in some way: for example, a validator that enforces a minimum string length
+  needs to know what that minimum string length is.  Such values are specified after an equals sign.
+  
+  For example:  <<<validator:required,minLength=5>>> would presumably enforce that a field
+  requires a value, with at least five characters.
+  
+  TODO: More ability to escape or quote constraint values. Ability to reference methods
+  or properties of the container to perform some of the validation.  Links to
+  proper discussion of validation, once the code and documentation is ready.
+  
 Parameters Are Bi-Directional
 
   Parameters are not simply variables; each parameter represents a connection, or <binding>, between
@@ -190,8 +209,8 @@
 
   (Though the whitespace will be quite different.)  
   
-  The relevant part is that components can read fixed values, or live properties of their
-  container, and can <change> properties of their container as well.
+  The relevant part is that components can read fixed values, or <live>
+  properties of their  container, and can <change> properties of their container as well.
   
 Required Parameters
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/TapestryUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/TapestryUtilsTest.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/TapestryUtilsTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/TapestryUtilsTest.java Thu Dec 21 15:09:45 2006
@@ -75,4 +75,20 @@
         { "z", "z" },
         { "0abc", "0abc" } };
     }
+
+    @Test(dataProvider = "to_user_presentable")
+    public void to_user_presentable(String input, String expected)
+    {
+        assertEquals(TapestryUtils.toUserPresentable(input), expected);
+    }
+
+    @DataProvider(name = "to_user_presentable")
+    public Object[][] to_user_presentable_data()
+    {
+        return new Object[][]
+        {
+        { "hello", "Hello" },
+        { "userId", "User Id" },
+        { "useHTML", "Use HTML" }, };
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/BindingFactoryTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/BindingFactoryTest.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/BindingFactoryTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/BindingFactoryTest.java Thu Dec 21 15:09:45 2006
@@ -41,7 +41,7 @@
 
         BindingFactory factory = new LiteralBindingFactory();
 
-        Binding b = factory.newBinding("test binding", res, "Tapestry5", l);
+        Binding b = factory.newBinding("test binding", res, null, "Tapestry5", l);
 
         assertSame(InternalUtils.locationOf(b), l);
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java?view=diff&rev=489493&r1=489492&r2=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java Thu Dec 21 15:09:45 2006
@@ -56,11 +56,6 @@
         return resources;
     }
 
-    private void train_getComponent(ComponentResources resources, Component component)
-    {
-        expect(resources.getComponent()).andReturn(component);
-    }
-
     @Test
     public void object_property()
     {
@@ -70,7 +65,7 @@
 
         replay();
 
-        Binding binding = _factory.newBinding("test binding", resources, "objectValue", l);
+        Binding binding = _factory.newBinding("test binding", resources, null, "objectValue", l);
 
         assertSame(binding.getBindingType(), String.class);
 
@@ -97,7 +92,12 @@
 
         replay();
 
-        Binding binding = _factory.newBinding("test binding", resources, "stringHolder.value", l);
+        Binding binding = _factory.newBinding(
+                "test binding",
+                resources,
+                null,
+                "stringHolder.value",
+                l);
 
         assertSame(binding.getBindingType(), String.class);
 
@@ -132,7 +132,7 @@
 
         try
         {
-            _factory.newBinding("test binding", resources, propertyPath, l);
+            _factory.newBinding("test binding", resources, null, propertyPath, l);
             unreachable();
         }
         catch (TapestryException ex)
@@ -162,7 +162,7 @@
 
         try
         {
-            _factory.newBinding("test binding", resources, propertyPath, l);
+            _factory.newBinding("test binding", resources, null, propertyPath, l);
             unreachable();
         }
         catch (TapestryException ex)
@@ -187,7 +187,7 @@
 
         replay();
 
-        Binding binding = _factory.newBinding("test binding", resources, "intValue", l);
+        Binding binding = _factory.newBinding("test binding", resources, null, "intValue", l);
 
         assertSame(binding.getBindingType(), int.class);
 
@@ -211,7 +211,7 @@
 
         replay();
 
-        Binding binding = _factory.newBinding("test binding", resources, "readOnly", l);
+        Binding binding = _factory.newBinding("test binding", resources, null, "readOnly", l);
 
         assertEquals(binding.get(), "ReadOnly");
 
@@ -240,7 +240,7 @@
 
         replay();
 
-        Binding binding = _factory.newBinding("test binding", resources, "writeOnly", l);
+        Binding binding = _factory.newBinding("test binding", resources, null, "writeOnly", l);
 
         binding.set("updated");
 
@@ -281,8 +281,8 @@
 
         replay();
 
-        Binding binding1 = factory.newBinding("test binding 1", resources1, "objectValue", l);
-        Binding binding2 = factory.newBinding("test binding 2", resources2, "objectValue", l);
+        Binding binding1 = factory.newBinding("test binding 1", resources1, null, "objectValue", l);
+        Binding binding2 = factory.newBinding("test binding 2", resources2, null, "objectValue", l);
 
         assertSame(binding2.getClass(), binding1.getClass());
 
@@ -296,7 +296,7 @@
 
         factory.objectWasInvalidated();
 
-        Binding binding3 = factory.newBinding("test binding 3", resources3, "objectValue", l);
+        Binding binding3 = factory.newBinding("test binding 3", resources3, null, "objectValue", l);
 
         // We'll assume the behavior is the same, but expect the class to be different.
 
@@ -318,7 +318,7 @@
 
         try
         {
-            _factory.newBinding("test binding", resources, "missingProperty", l);
+            _factory.newBinding("test binding", resources, null, "missingProperty", l);
             unreachable();
         }
         catch (TapestryException ex)
@@ -345,7 +345,7 @@
 
         replay();
 
-        Binding binding = _factory.newBinding(description, resources, "this", l);
+        Binding binding = _factory.newBinding(description, resources, null, "this", l);
 
         assertSame(binding.get(), component);
 
@@ -361,7 +361,7 @@
 
         replay();
 
-        Binding binding = _factory.newBinding(description, resources, expression, l);
+        Binding binding = _factory.newBinding(description, resources, null, expression, l);
 
         assertEquals(binding.get(), expected);
 

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactoryTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactoryTest.java?view=auto&rev=489493
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactoryTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/ValidatorBindingFactoryTest.java Thu Dec 21 15:09:45 2006
@@ -0,0 +1,93 @@
+// Copyright 2006 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.internal.bindings;
+
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.Field;
+import org.apache.tapestry.FieldValidator;
+import org.apache.tapestry.ioc.Location;
+import org.apache.tapestry.ioc.internal.util.TapestryException;
+import org.apache.tapestry.ioc.services.FieldValidatorSource;
+import org.apache.tapestry.runtime.Component;
+import org.apache.tapestry.services.Binding;
+import org.apache.tapestry.services.BindingFactory;
+import org.apache.tapestry.test.TapestryTestCase;
+import org.testng.annotations.Test;
+
+public class ValidatorBindingFactoryTest extends TapestryTestCase
+{
+    private interface FieldComponent extends Field, Component
+    {
+    };
+
+    @Test
+    public void not_a_field()
+    {
+        FieldValidatorSource source = newFieldValidatorSource();
+        ComponentResources container = newComponentResources();
+        ComponentResources component = newComponentResources();
+        Component instance = newComponent();
+        Location l = newLocation();
+
+        train_getComponent(component, instance);
+        train_getCompleteId(component, "foo.Bar:baz");
+
+        replay();
+
+        BindingFactory factory = new ValidatorBindingFactory(source);
+
+        try
+        {
+            factory.newBinding("descrip", container, component, "zip,zoom", l);
+        }
+        catch (TapestryException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Component 'foo.Bar:baz' is not a field (it does not implement the Field interface) and may not be used with the validator: binding prefix.");
+            assertSame(ex.getLocation(), l);
+        }
+
+        verify();
+    }
+
+    @Test
+    public void success()
+    {
+
+        FieldValidatorSource source = newFieldValidatorSource();
+        ComponentResources container = newComponentResources();
+        ComponentResources component = newComponentResources();
+        FieldComponent instance = newMock(FieldComponent.class);
+        Location l = newLocation();
+        FieldValidator validator = newFieldValidator();
+
+        String expression = "required,minLength=5";
+
+        train_getComponent(component, instance);
+
+        expect(source.createValidators(instance, expression)).andReturn(validator);
+
+        replay();
+
+        BindingFactory factory = new ValidatorBindingFactory(source);
+
+        Binding binding = factory.newBinding("descrip", container, component, expression, l);
+
+        assertSame(binding.get(), validator);
+
+        verify();
+    }
+}