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/09/04 19:34:29 UTC

svn commit: r440132 - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/ main/java/org/apache/tapestry/internal/ main/java/org/apache/tapestry/internal/bindings/ main/java/org/apache/tapestry/internal/ioc/services/ main/java...

Author: hlship
Date: Mon Sep  4 10:34:27 2006
New Revision: 440132

URL: http://svn.apache.org/viewvc?view=rev&rev=440132
Log:
Add "prop:" binding, which allows a component to bind to a named property of its containing component.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BasePropBinding.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/PropBindingFactory.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/MerryChristmas.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.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/bindings/TargetBean.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/MerryChristmas.html
Removed:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryClassLoader.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Binding.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalConstants.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/LocationImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryException.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/AbstractBinding.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/ioc/services/ClassFactoryClassPool.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/CtClassSource.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentInstantiatorSourceImpl.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/PageLoaderImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHubImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassFabUtils.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/bindings/BindingsStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/LocationImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ClassFabImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/UpdateListenerHubImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/ClassFabUtilsTest.java

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Binding.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Binding.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Binding.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Binding.java Mon Sep  4 10:34:27 2006
@@ -26,13 +26,16 @@
 public interface Binding
 {
     /**
-     * Reads the current value of the property (or other resource).
+     * Reads the current value of the property (or other resource). When reading properties of
+     * objects that are primitive types, this will return an instance of the wrapper type. In some
+     * cases, a binding is read only and this method will throw a runtime exception.
      */
     Object get();
 
     /**
-     * Updates the current value. Most types of bindings are read-only, and this method will throw
-     * an exception.
+     * Updates the current value. Most types of bindings are read-only, and this method will throw a
+     * runtime exception. It is the caller's responsibility to ensure that the value passed in is of
+     * the appropriate type.
      * 
      * @param value
      */

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalConstants.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalConstants.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalConstants.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalConstants.java Mon Sep  4 10:34:27 2006
@@ -22,7 +22,6 @@
 @Utility
 public final class InternalConstants
 {
-
     /**
      * Init parameter used to identify the package from which application classes are loaded. Such
      * classes are in the pages, components and mixins sub-packages.
@@ -31,4 +30,7 @@
 
     /** Binding expression prefix used for literal strings. */
     public static final String LITERAL_BINDING_PREFIX = "literal";
+
+    /** Binding expression prefix used to bind to a property of the component. */
+    public static final String PROP_BINDING_PREFIX = "prop";
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/LocationImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/LocationImpl.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/LocationImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/LocationImpl.java Mon Sep  4 10:34:27 2006
@@ -69,18 +69,14 @@
     @Override
     public String toString()
     {
-        StringBuilder buffer = new StringBuilder();
+        StringBuilder buffer = new StringBuilder(_resource.toString());
         Formatter formatter = new Formatter(buffer);
 
-        formatter.format("<Location: %s", _resource);
-
         if (_line != UNKNOWN)
             formatter.format(", line %d", _line);
 
         if (_column != UNKNOWN)
             formatter.format(", column %d", _column);
-
-        formatter.format(">");
 
         return buffer.toString();
     }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryException.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryException.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryException.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/TapestryException.java Mon Sep  4 10:34:27 2006
@@ -64,4 +64,14 @@
     {
         return _location;
     }
+
+    @Override
+    public String toString()
+    {
+        if (_location == null)
+            return super.toString();
+
+        return String.format("%s [at %s]", super.toString(), _location);
+    }
+
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/AbstractBinding.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/AbstractBinding.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/AbstractBinding.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/AbstractBinding.java Mon Sep  4 10:34:27 2006
@@ -35,7 +35,7 @@
     }
 
     /**
-     * @throws UnsupportedOperationException
+     * @throws TapestryException
      *             always
      */
     public void set(Object value)

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BasePropBinding.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BasePropBinding.java?view=auto&rev=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BasePropBinding.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/BasePropBinding.java Mon Sep  4 10:34:27 2006
@@ -0,0 +1,42 @@
+package org.apache.tapestry.internal.bindings;
+
+import org.apache.tapestry.Location;
+import org.apache.tapestry.internal.TapestryException;
+
+/**
+ * Base class for bindings created by the
+ * {@link org.apache.tapestry.internal.bindings.PropBindingFactory}. A subclass of this is created
+ * at runtime.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public abstract class BasePropBinding extends AbstractBinding
+{
+    private final String _toString;
+
+    public BasePropBinding(String toString, Location location)
+    {
+        super(location);
+
+        _toString = toString;
+    }
+
+    /** The default implementation of get() will throw a TapestryException (binding is write only). */
+    public Object get()
+    {
+        throw new TapestryException(BindingsMessages.bindingIsWriteOnly(this), this, null);
+    }
+
+    @Override
+    public String toString()
+    {
+        return _toString;
+    }
+
+    /** Returns false; these properties are always dynamic. */
+    @Override
+    public boolean isInvariant()
+    {
+        return false;
+    }
+}
\ No newline at end of file

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=440132&r1=440131&r2=440132
==============================================================================
--- 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 Mon Sep  4 10:34:27 2006
@@ -31,4 +31,14 @@
     {
         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)
+    {
+        return MESSAGES.format("no-such-property", targetClass.getName(), propertyName);
+    }
 }

Added: 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=auto&rev=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/PropBindingFactory.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/bindings/PropBindingFactory.java Mon Sep  4 10:34:27 2006
@@ -0,0 +1,170 @@
+package org.apache.tapestry.internal.bindings;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+
+import org.apache.tapestry.Binding;
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.Location;
+import org.apache.tapestry.events.InvalidationEvent;
+import org.apache.tapestry.events.InvalidationListener;
+import org.apache.tapestry.internal.TapestryException;
+import org.apache.tapestry.internal.annotations.Concurrent;
+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.services.BindingFactory;
+import org.apache.tapestry.util.BodyBuilder;
+import org.apache.tapestry.util.CollectionFactory;
+
+/**
+ * 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}.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+@Concurrent
+public class PropBindingFactory implements BindingFactory, InvalidationListener
+{
+    private final PropertyAccess _propertyAccess;
+
+    private final ClassFactory _classFactory;
+
+    private final Map<String, Class> _cache = CollectionFactory.newMap();
+
+    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)
+    {
+        _propertyAccess = propertyAccess;
+        _classFactory = classFactory;
+    }
+
+    public Binding newBinding(String description, ComponentResources component, String expression,
+            Location location)
+    {
+        Object target = component.getComponent();
+        Class targetClass = target.getClass();
+
+        try
+        {
+            Class bindingClass = findBindingClass(targetClass, expression);
+
+            String toString = String.format("PropBinding[%s %s(%s)]", description, component
+                    .getCompleteId(), expression);
+
+            Constructor cons = bindingClass.getConstructors()[0];
+
+            return (Binding) cons.newInstance(target, toString, location);
+        }
+        catch (Exception ex)
+        {
+            throw new TapestryException(ex.getMessage(), location, ex);
+        }
+    }
+
+    @Concurrent.Read
+    private Class findBindingClass(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;
+
+        Class result = _cache.get(key);
+
+        if (result == null)
+            result = fillCache(key, targetClass, propertyName);
+
+        return result;
+    }
+
+    @Concurrent.Write
+    private Class fillCache(String key, Class targetClass, String propertyName)
+    {
+        // 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.
+
+        PropertyAdapter adapter = _propertyAccess.getAdapter(targetClass).getPropertyAdapter(
+                propertyName);
+
+        if (adapter == null)
+            throw new RuntimeException(BindingsMessages.noSuchProperty(targetClass, propertyName));
+
+        Class bindingClass = createBindingClass(targetClass, propertyName, adapter);
+
+        _cache.put(key, bindingClass);
+
+        return bindingClass;
+    }
+
+    private Class createBindingClass(Class targetClass, 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, String.class, Location.class }, null, "{ super($2, $3); _target = $1; }");
+
+        if (adapter.isRead())
+        {
+            String body = String.format("return ($w) _target.%s();", 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.getWrapperType(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("_target.%s(value);", adapter.getWriteMethod().getName());
+            builder.end();
+
+            classFab.addMethod(Modifier.PUBLIC, SET_SIGNATURE, builder.toString());
+        }
+
+        return classFab.createClass();
+    }
+
+    @Concurrent.Write
+    public void objectWasInvalidated(InvalidationEvent event)
+    {
+        _cache.clear();
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryClassPool.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryClassPool.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryClassPool.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryClassPool.java Mon Sep  4 10:34:27 2006
@@ -28,14 +28,18 @@
 
 /**
  * Used to ensure that {@link javassist.ClassPool#appendClassPath(javassist.ClassPath)} is invoked
- * with a synchronized lock. Additionally, wraps around a shared
- * {@link org.apache.tapestry.internal.ioc.services.ClassFactoryClassLoader}.
+ * with a synchronized lock, and also handles tricky class loading issues (caused by the creation of
+ * classes, and class loaders, at runtime).
  * 
  * @author Howard Lewis Ship
  */
 class ClassFactoryClassPool extends ClassPool
 {
-    private ClassFactoryClassLoader _loader = new ClassFactoryClassLoader();
+    // This is the loader with the widest visibility yet seen.
+
+    private ClassLoader _loader;
+
+    private ClassPath _priorClassPath;
 
     /**
      * Used to identify which class loaders have already been integrated into the pool.
@@ -46,35 +50,47 @@
     {
         super(null);
 
-        appendClassLoader(contextClassLoader);
+        addClassLoaderIfNeeded(contextClassLoader);
     }
 
     /**
      * Convienience method for adding to the ClassPath for a particular class loader.
+     * <p>
+     * TODO: This code assumes that ClassLoaders are structured as a "line" not a proper "tree". That
+     * is, if the ClassLoader hiearchy actually does have branches, rather than a straight line from
+     * root to leaf, it may not work.
      * 
      * @param loader
      *            the class loader to add (derived from a loaded class, and may be null for some
      *            system classes)
      */
     @SuppressNullCheck
-    public synchronized void appendClassLoader(ClassLoader loader)
+    public synchronized void addClassLoaderIfNeeded(ClassLoader loader)
     {
         if (loader == null || loader == _loader || _loaders.contains(loader))
             return;
 
-        _loader.addDelegateLoader(loader);
-
         ClassPath path = new LoaderClassPath(loader);
 
+        if (_priorClassPath != null)
+            removeClassPath(_priorClassPath);
+
         appendClassPath(path);
 
-        _loaders.add(loader);
+        _priorClassPath = path;
+
+        ClassLoader l = loader;
+        while (l != null)
+        {
+            _loaders.add(l);
+            l = l.getParent();
+        }
+
+        _loader = loader;
     }
 
     /**
-     * Invoked to convert an fabricated class into a real class. The new classes' class loader will
-     * be the delegating ClassFactoryClassLoader, which has visibility to all class loaders for all
-     * modules.
+     * Invoked to convert an fabricated class into a real class.
      */
     @Override
     public synchronized Class toClass(CtClass ctClass) throws CannotCompileException

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryImpl.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFactoryImpl.java Mon Sep  4 10:34:27 2006
@@ -40,9 +40,9 @@
 
     private final ClassLoader _loader;
 
-    public ClassFactoryImpl(ClassLoader contextClassLoader)
+    public ClassFactoryImpl(ClassLoader classLoader)
     {
-        this(contextClassLoader, LogFactory.getLog(ClassFactoryImpl.class));
+        this(classLoader, LogFactory.getLog(ClassFactoryImpl.class));
     }
 
     public ClassFactoryImpl()
@@ -50,11 +50,12 @@
         this(Thread.currentThread().getContextClassLoader());
     }
 
-    public ClassFactoryImpl(ClassLoader contextClassLoader, Log log)
+    /** Main constructor where a specific class loader and log is provided. */
+    public ClassFactoryImpl(ClassLoader classLoader, Log log)
     {
-        _loader = contextClassLoader;
+        _loader = classLoader;
 
-        _pool = new ClassFactoryClassPool(contextClassLoader);
+        _pool = new ClassFactoryClassPool(classLoader);
 
         _classSource = new CtClassSource(_pool);
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/CtClassSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/CtClassSource.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/CtClassSource.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/CtClassSource.java Mon Sep  4 10:34:27 2006
@@ -20,10 +20,8 @@
 import org.apache.tapestry.ioc.services.ClassFabUtils;
 
 /**
- * Wrapper around Javassist's {@link javassist.ClassPool} and our own
- * {@link org.apache.tapestry.internal.ioc.services.ClassFactoryClassLoader} that manages the
- * creation of new instances of {@link javassist.CtClass} and converts finished CtClass's into
- * instantiable Classes.
+ * Wrapper around Javassist's {@link javassist.ClassPool} that manages the creation of new instances
+ * of {@link javassist.CtClass} and converts finished CtClass's into instantiable Classes.
  * 
  * @author Howard Lewis Ship
  */
@@ -53,7 +51,7 @@
         // Add the class loader for the searchClass to the class pool and
         // delegating class loader if needed.
 
-        _pool.appendClassLoader(loader);
+        _pool.addClassLoaderIfNeeded(loader);
 
         String name = ClassFabUtils.getJavaClassName(searchClass);
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentInstantiatorSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentInstantiatorSourceImpl.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentInstantiatorSourceImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentInstantiatorSourceImpl.java Mon Sep  4 10:34:27 2006
@@ -32,7 +32,6 @@
 import org.apache.tapestry.events.UpdateListener;
 import org.apache.tapestry.internal.event.InvalidationEventHubImpl;
 import org.apache.tapestry.internal.util.URLChangeTracker;
-import org.apache.tapestry.ioc.services.ClassFactory;
 import org.apache.tapestry.model.ComponentModel;
 import org.apache.tapestry.util.Defense;
 
@@ -58,8 +57,6 @@
 
     private Loader _loader;
 
-    private ClassFactory _classFactory;
-
     private final ComponentClassTransformer _transformer;
 
     private final Log _log;
@@ -67,25 +64,8 @@
     /** Map from class name to Instantiator. */
     private final Map<String, Instantiator> _instantiatorMap = newMap();
 
-    /** @return the class loader used when loading enhanced/modified classes */
-    public ClassLoader getClassLoader()
-    {
-        return _loader;
-    }
-
-    public ComponentInstantiatorSourceImpl(ClassLoader parent,
-            ComponentClassTransformer transformer, Log log)
-    {
-        _parent = parent;
-        _transformer = transformer;
-        _log = log;
-
-        initializeService();
-    }
-
     private class PackageAwareLoader extends Loader
     {
-
         public PackageAwareLoader(ClassLoader parent, ClassPool classPool)
         {
             super(parent, classPool);
@@ -102,6 +82,22 @@
             return null;
         }
 
+    }
+
+    public ComponentInstantiatorSourceImpl(ClassLoader parent,
+            ComponentClassTransformer transformer, Log log)
+    {
+        _parent = parent;
+        _transformer = transformer;
+        _log = log;
+
+        initializeService();
+    }
+
+    /** @return the class loader used when loading enhanced/modified classes */
+    public ClassLoader getClassLoader()
+    {
+        return _loader;
     }
 
     public synchronized void checkForUpdates(UpdateEvent event)

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=440132&r1=440131&r2=440132
==============================================================================
--- 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 Mon Sep  4 10:34:27 2006
@@ -22,7 +22,10 @@
 import org.apache.tapestry.annotations.BeforeRenderBody;
 import org.apache.tapestry.annotations.RenderCloseTag;
 import org.apache.tapestry.annotations.RenderTag;
+import org.apache.tapestry.events.InvalidationEvent;
+import org.apache.tapestry.events.InvalidationListener;
 import org.apache.tapestry.internal.InternalConstants;
+import org.apache.tapestry.internal.bindings.PropBindingFactory;
 import org.apache.tapestry.ioc.IOCUtilities;
 import org.apache.tapestry.ioc.LogSource;
 import org.apache.tapestry.ioc.OrderedConfiguration;
@@ -34,9 +37,11 @@
 import org.apache.tapestry.ioc.annotations.Order;
 import org.apache.tapestry.ioc.services.ClassFactory;
 import org.apache.tapestry.ioc.services.LoggingDecorator;
+import org.apache.tapestry.ioc.services.PropertyAccess;
 import org.apache.tapestry.ioc.services.ThreadCleanupHub;
 import org.apache.tapestry.services.ApplicationInitializer;
 import org.apache.tapestry.services.ApplicationInitializerFilter;
+import org.apache.tapestry.services.BindingFactory;
 import org.apache.tapestry.services.BindingSource;
 import org.apache.tapestry.services.ComponentClassResolver;
 import org.apache.tapestry.services.ComponentClassTransformWorker;
@@ -255,11 +260,12 @@
      */
     @Contribute("tapestry.ApplicationInitializer")
     public void contributeApplicationInitializerFilters(
-            OrderedConfiguration<ApplicationInitializerFilter> configuration)
+            OrderedConfiguration<ApplicationInitializerFilter> configuration,
+            @InjectService("tapestry.ioc.PropertyAccess")
+            final PropertyAccess propertyAccess)
     {
-        ApplicationInitializerFilter filter = new ApplicationInitializerFilter()
+        ApplicationInitializerFilter setApplicationPackage = new ApplicationInitializerFilter()
         {
-
             public void initializeApplication(WebContext context, ApplicationInitializer initializer)
             {
                 String packageName = context
@@ -272,7 +278,42 @@
 
         };
 
-        configuration.add("SetApplicationPackage", filter, "before:*.*");
+        configuration.add("SetApplicationPackage", setApplicationPackage, "before:*.*");
+
+        final InvalidationListener listener = new InvalidationListener()
+        {
+            public void objectWasInvalidated(InvalidationEvent event)
+            {
+                propertyAccess.clear();
+            }
+
+        };
+
+        ApplicationInitializerFilter clearPropertyAccess = new ApplicationInitializerFilter()
+        {
+            public void initializeApplication(WebContext context, ApplicationInitializer initializer)
+            {
+                // Snuck in here is the logic to clear the PropertyAccess service's cache whenever
+                // the component class loader is invalidated.
+
+                _componentInstantiatorSource.addInvalidationListener(listener);
+
+                initializer.initializeApplication(context);
+            }
+        };
+
+        configuration.add("ClearPropertyAccessCacheOnInvalidation", clearPropertyAccess);
+    }
+
+    public BindingFactory buildPropBindingFactory(@InjectService("tapestry.ioc.PropertyAccess")
+    PropertyAccess propertyAccess, @InjectService("tapestry.ComponentClassFactory")
+    ClassFactory classFactory)
+    {
+        PropBindingFactory service = new PropBindingFactory(propertyAccess, classFactory);
+
+        _componentInstantiatorSource.addInvalidationListener(service);
 
+        return service;
     }
+
 }

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=440132&r1=440131&r2=440132
==============================================================================
--- 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 Mon Sep  4 10:34:27 2006
@@ -18,6 +18,7 @@
 import java.util.Locale;
 
 import org.apache.tapestry.Binding;
+import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.events.InvalidationEvent;
 import org.apache.tapestry.events.InvalidationListener;
 import org.apache.tapestry.internal.InternalConstants;
@@ -84,6 +85,12 @@
 
             workComponentQueue();
 
+            // The page is *loaded* before it is attached to the request.
+            // This is to help ensure that no client-specific information leaks
+            // into the page.
+
+            _page.loaded();
+
             return _page;
         }
         finally
@@ -102,6 +109,7 @@
                 componentName);
 
         _page.setRootElement(rootComponent);
+        _page.addLifecycleListener(rootComponent.getComponent());
 
         push(_componentQueue, rootComponent);
     }
@@ -219,6 +227,8 @@
 
                     add(loadingComponent, activeComponent, newComponent);
 
+                    _page.addLifecycleListener(newComponent.getComponent());
+
                     // Make sure container knows about the new component.
 
                     loadingComponent.addChild(newComponent);
@@ -250,7 +260,7 @@
 
                     if (directlyInsideSubcomponent)
                     {
-                        addBindingToComponent(activeComponent, attribute);
+                        addBindingToComponent(loadingComponent, activeComponent, attribute);
                     }
                     else
                         add(loadingComponent, activeComponent, _pageElementFactory
@@ -265,7 +275,8 @@
 
     }
 
-    private void addBindingToComponent(ComponentPageElement component, AttributeToken token)
+    private void addBindingToComponent(ComponentResources loadingComponent,
+            ComponentPageElement component, AttributeToken token)
     {
         String name = token.getName();
         String description = "parameter " + name;
@@ -274,7 +285,7 @@
 
         Binding binding = _bindingSource.newBinding(
                 description,
-                component,
+                loadingComponent,
                 InternalConstants.LITERAL_BINDING_PREFIX,
                 token.getValue(),
                 token.getLocation());

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHub.java Mon Sep  4 10:34:27 2006
@@ -22,7 +22,8 @@
  * locked down so that only a single thread is active, and the active thread invokes
  * {@link #fireUpdateEvent()}. Various services that are dependent on external resource files (such
  * as classes or template files) can check to see if any file they've used has changed. If so, the
- * service can invalidate its internal cache, or notify other services that they should do the same.
+ * service can invalidate its internal cache, or notify other services (typically via
+ * {@link org.apache.tapestry.events.InvalidationListener} that they should do the same.
  * 
  * @author Howard M. Lewis Ship
  * @see org.apache.tapestry.internal.util.URLChangeTracker
@@ -30,9 +31,6 @@
 public interface UpdateListenerHub
 {
     void addUpdateListener(UpdateListener listener);
-
-    /** For completeness. */
-    void removeUpdateListener(UpdateListener listener);
 
     /**
      * Invoked periodically to allow services to check if underlying state has changed. For example,

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHubImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHubImpl.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHubImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/UpdateListenerHubImpl.java Mon Sep  4 10:34:27 2006
@@ -43,11 +43,4 @@
             listener.checkForUpdates(event);
         }
     }
-
-    public void removeUpdateListener(UpdateListener listener)
-    {
-        _listeners.remove(listener);
-
-    }
-
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java Mon Sep  4 10:34:27 2006
@@ -71,6 +71,20 @@
 
     private boolean _rendering;
 
+    private static final Map<Class, Class> PRIMITIVE_TO_WRAPPER = newMap();
+
+    static
+    {
+        PRIMITIVE_TO_WRAPPER.put(boolean.class, Boolean.class);
+        PRIMITIVE_TO_WRAPPER.put(byte.class, Byte.class);
+        PRIMITIVE_TO_WRAPPER.put(short.class, Short.class);
+        PRIMITIVE_TO_WRAPPER.put(int.class, Integer.class);
+        PRIMITIVE_TO_WRAPPER.put(long.class, Long.class);
+        PRIMITIVE_TO_WRAPPER.put(char.class, Character.class);
+        PRIMITIVE_TO_WRAPPER.put(float.class, Float.class);
+        PRIMITIVE_TO_WRAPPER.put(double.class, Double.class);
+    }
+
     /** Constructor for the root component of a page. */
     public ComponentPageElementImpl(Page page, Instantiator instantiator, ComponentModel model)
     {
@@ -408,6 +422,7 @@
         return b != null && b.isInvariant();
     }
 
+    @SuppressWarnings("unchecked")
     public <T> T readParameter(String parameterName, Class<T> expectedType)
     {
         Binding b = getBinding(parameterName);
@@ -415,6 +430,12 @@
         // TODO: If binding is null ...
 
         // TODO: Type coercion to expected type
+
+        // Easier to do primitive-to-wrapper conversions in this code than in
+        // the generated component code.
+
+        if (expectedType.isPrimitive())
+            expectedType = PRIMITIVE_TO_WRAPPER.get(expectedType);
 
         return expectedType.cast(b.get());
     }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassFabUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassFabUtils.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassFabUtils.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/services/ClassFabUtils.java Mon Sep  4 10:34:27 2006
@@ -83,22 +83,55 @@
         return method.getReturnType().equals(String.class);
     }
 
-    /**
-     * Mapping between a primitive type and its Java VM representation Used for the encoding of
-     * array types
-     */
-    private static Map<String, String> PRIMITIVE_TYPE_CODES = newMap();
+    private static class PrimitiveInfo
+    {
+        private final String _typeCode;
+
+        private final String _wrapperType;
+
+        private final String _unwrapMethod;
+
+        public PrimitiveInfo(String typeCode, String wrapperType, String unwrapMethod)
+        {
+            _typeCode = typeCode;
+            _wrapperType = wrapperType;
+            _unwrapMethod = unwrapMethod;
+        }
+
+        public String getTypeCode()
+        {
+            return _typeCode;
+        }
+
+        public String getUnwrapMethod()
+        {
+            return _unwrapMethod;
+        }
+
+        public String getWrapperType()
+        {
+            return _wrapperType;
+        }
+    }
+
+    private static final Map<String, PrimitiveInfo> PRIMITIVE_INFO = newMap();
 
     static
     {
-        PRIMITIVE_TYPE_CODES.put("boolean", "Z");
-        PRIMITIVE_TYPE_CODES.put("short", "S");
-        PRIMITIVE_TYPE_CODES.put("int", "I");
-        PRIMITIVE_TYPE_CODES.put("long", "J");
-        PRIMITIVE_TYPE_CODES.put("float", "F");
-        PRIMITIVE_TYPE_CODES.put("double", "D");
-        PRIMITIVE_TYPE_CODES.put("char", "C");
-        PRIMITIVE_TYPE_CODES.put("byte", "B");
+        add("boolean", "Z", "java.lang.Boolean", "booleanValue");
+        add("short", "S", "java.lang.Short", "shortValue");
+        add("int", "I", "java.lang.Integer", "intValue");
+        add("long", "J", "java.lang.Long", "longValue");
+        add("float", "F", "java.lang.Float", "floatValue");
+        add("double", "D", "java.lang.Double", "doubleValue");
+        add("char", "C", "java.lang.Character", "charValue");
+        add("byte", "B", "java.lang.Byte", "byteValue");
+    }
+
+    private static void add(String primitiveType, String typeCode, String wrapperType,
+            String unwrapMethod)
+    {
+        PRIMITIVE_INFO.put(primitiveType, new PrimitiveInfo(typeCode, wrapperType, unwrapMethod));
     }
 
     /**
@@ -121,9 +154,10 @@
             type = type.substring(0, type.length() - 2);
         }
 
-        String primitiveIdentifier = (String) PRIMITIVE_TYPE_CODES.get(type);
-        if (primitiveIdentifier != null)
-            buffer.append(primitiveIdentifier);
+        PrimitiveInfo pi = PRIMITIVE_INFO.get(type);
+
+        if (pi != null)
+            buffer.append(pi.getTypeCode());
         else
         {
             buffer.append("L");
@@ -132,5 +166,21 @@
         }
 
         return buffer.toString();
+    }
+
+    /**
+     * Given one of the primitive types, returns the name of the method that will unwrap the wrapped
+     * type to the primitive type.
+     */
+    public static String getUnwrapMethodName(String primitiveTypeName)
+    {
+        return PRIMITIVE_INFO.get(primitiveTypeName).getUnwrapMethod();
+    }
+
+    /** Given the name of a primitive type, returns the name of the corresponding wrapper class. */
+
+    public static String getWrapperType(String primitiveType)
+    {
+        return PRIMITIVE_INFO.get(primitiveType).getWrapperType();
     }
 }

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=440132&r1=440131&r2=440132
==============================================================================
--- 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 Mon Sep  4 10:34:27 2006
@@ -363,9 +363,12 @@
     }
 
     /** Contributes the factory for "literal:" bindings. */
-    public void contributeBindingSource(MappedConfiguration<String, BindingFactory> configuration)
+    public void contributeBindingSource(MappedConfiguration<String, BindingFactory> configuration,
+            @InjectService("tapestry.internal.PropBindingFactory")
+            BindingFactory propBindingFactory)
     {
         configuration.add(InternalConstants.LITERAL_BINDING_PREFIX, new LiteralBindingFactory());
+        configuration.add(InternalConstants.PROP_BINDING_PREFIX, propBindingFactory);
     }
 
     /**

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=440132&r1=440131&r2=440132
==============================================================================
--- 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 Mon Sep  4 10:34:27 2006
@@ -12,4 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-binding-is-read-only=Binding %s is read-only.
\ No newline at end of file
+binding-is-read-only=Binding %s is read-only.
+binding-is-write-only=Binding %s is write-only.
+no-such-property=Class %s does not contain a property named '%s'.
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties Mon Sep  4 10:34:27 2006
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 unable-to-add-method=Unable to add method %s to class %s: %s
+unable-to-add-field=Unable to add field %s to class %s: %s
 unable-to-add-constructor=Unable to add constructor to class %s: %s
 unable-to-create-class=Unable to create class %s as subclass of %s: %s
 unable-to-lookup-class=Unable to lookup class %s: %s

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html Mon Sep  4 10:34:27 2006
@@ -10,8 +10,13 @@
         </p>
         <p>
             Tapestry 5 Integration Application 1:
-            <a href="Start.html">Start Page</a>
+
+            <ul>
+                <a href="Start.html">Start Page</a>
+                <a href="MerryChristmas.html">Loop Page</a>
+            </ul>
         </p>
+        
         
         <p>
             Feast your eyes:            

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java Mon Sep  4 10:34:27 2006
@@ -32,16 +32,16 @@
 { "integration" })
 public class IntegrationTests extends Assert
 {
+    private static final String BASE_URL = "http://localhost/";
+
+    public static final String PAGE_LOAD_TIMEOUT = "1000";
+
     private Selenium _selenium;
 
     private SeleniumServer _server;
 
     private JettyRunner _jettyRunner;
 
-    public static final String BASE_URL = String.format(
-            "http://localhost:%d",
-            SeleniumServer.DEFAULT_PORT);
-
     @BeforeClass
     public void startupBackground() throws Exception
     {
@@ -81,12 +81,10 @@
         _jettyRunner = null;
     }
 
-    public static final String PAGE_LOAD_TIMEOUT = "1000";
-
     @Test
-    public void app1_basic_output() throws Exception
+    public void app1_basic_output_and_parameters() throws Exception
     {
-        _selenium.open("http://localhost/");
+        _selenium.open(BASE_URL);
 
         _selenium.click("link=Start Page");
         _selenium.waitForPageToLoad(PAGE_LOAD_TIMEOUT);
@@ -104,5 +102,20 @@
         // This is text passed from Start.html to Output as a parameter
 
         assertTrue(body.contains("we have basic parameters working"));
+
+        // OK ... this could be a separate test, but for efficiency, we'll mix it in here.
+        // It takes a while to start up Selenium RC (and a Firefox browser).
+
+        _selenium.open(BASE_URL);
+
+        _selenium.click("link=Loop Page");
+        _selenium.waitForPageToLoad(PAGE_LOAD_TIMEOUT);
+
+        body = _selenium.getBodyText();
+
+        // Selenium is nice enough to remove the elements and excess whitespace.
+
+        assertTrue(body.contains("Ho! Ho! Ho!"));
     }
+
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java?view=auto&rev=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/Loop.java Mon Sep  4 10:34:27 2006
@@ -0,0 +1,39 @@
+package org.apache.tapestry.integration.app1.components;
+
+import org.apache.tapestry.annotations.BeforeRender;
+import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.Parameter;
+import org.apache.tapestry.annotations.RenderCloseTag;
+
+@ComponentClass
+public class Loop
+{
+    @Parameter(required = true)
+    private int _min;
+
+    @Parameter(required = true)
+    private int _max;
+
+    @Parameter(required = true)
+    private int _value;
+
+    @BeforeRender
+    void initializeValue()
+    {
+        _value = _min;
+    }
+
+    @RenderCloseTag
+    boolean startLoop()
+    {
+        int newValue = _value + 1;
+
+        if (newValue <= _max)
+        {
+            _value = newValue;
+            return true;
+        }
+
+        return false;
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/MerryChristmas.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/MerryChristmas.java?view=auto&rev=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/MerryChristmas.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/MerryChristmas.java Mon Sep  4 10:34:27 2006
@@ -0,0 +1,30 @@
+package org.apache.tapestry.integration.app1.pages;
+
+import org.apache.tapestry.annotations.ComponentClass;
+
+@ComponentClass
+public class MerryChristmas
+{
+    private int _value;
+
+    public int getMin()
+    {
+        return 0;
+    }
+
+    public int getMax()
+    {
+        return 2;
+    }
+
+    public int getValue()
+    {
+        return _value;
+    }
+
+    public void setValue(int value)
+    {
+        _value = value;
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/LocationImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/LocationImplTest.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/LocationImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/LocationImplTest.java Mon Sep  4 10:34:27 2006
@@ -63,10 +63,7 @@
         assertEquals(l.getLine(), line);
         assertEquals(l.getColumn(), column);
 
-        assertEquals(l.toString(), String.format(
-                "<Location: <Resource>, line %d, column %d>",
-                line,
-                column));
+        assertEquals(l.toString(), String.format("<Resource>, line %d, column %d", line, column));
     }
 
     @Test
@@ -82,7 +79,7 @@
         assertEquals(l.getLine(), line);
         assertEquals(l.getColumn(), -1);
 
-        assertEquals(l.toString(), String.format("<Location: <Resource>, line %d>", line));
+        assertEquals(l.toString(), String.format("<Resource>, line %d", line));
     }
 
     @Test
@@ -96,6 +93,6 @@
         assertEquals(l.getLine(), -1);
         assertEquals(l.getColumn(), -1);
 
-        assertEquals(l.toString(), "<Location: <Resource>>");
+        assertEquals(l.toString(), "<Resource>");
     }
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.java?view=auto&rev=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.java Mon Sep  4 10:34:27 2006
@@ -0,0 +1,57 @@
+package org.apache.tapestry.internal.bindings;
+
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.runtime.ComponentLifecycle;
+import org.apache.tapestry.runtime.LifecycleEvent;
+
+/**
+ * For use in places where we don't want to have to transform a class just for testing purposes.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public class DefaultComponentLifecyle implements ComponentLifecycle
+{
+
+    public void cleanupAfterRender()
+    {
+    }
+
+    public void beforeRender(MarkupWriter writer, LifecycleEvent<Boolean> event)
+    {
+    }
+
+    public void renderTag(MarkupWriter writer, LifecycleEvent<Boolean> event)
+    {
+    }
+
+    public void beforeRenderBody(MarkupWriter writer, LifecycleEvent<Boolean> event)
+    {
+    }
+
+    public void renderCloseTag(MarkupWriter writer, LifecycleEvent<Boolean> event)
+    {
+    }
+
+    public void afterRender(MarkupWriter writer, LifecycleEvent<Boolean> event)
+    {
+    }
+
+    public ComponentResources getResources()
+    {
+        return null;
+    }
+
+    public void containingPageDidLoad()
+    {
+    }
+
+    public void containingPageDidDetach()
+    {
+    }
+
+    public void containingPageDidAttach()
+    {
+    }
+
+}

Added: 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=auto&rev=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/PropBindingFactoryTest.java Mon Sep  4 10:34:27 2006
@@ -0,0 +1,223 @@
+package org.apache.tapestry.internal.bindings;
+
+import org.apache.tapestry.Binding;
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.Location;
+import org.apache.tapestry.events.InvalidationEvent;
+import org.apache.tapestry.internal.ioc.IOCUtilities;
+import org.apache.tapestry.internal.ioc.services.ClassFactoryImpl;
+import org.apache.tapestry.internal.ioc.services.PropertyAccessImpl;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.runtime.ComponentLifecycle;
+import org.testng.annotations.Test;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+public class PropBindingFactoryTest extends InternalBaseTestCase
+{
+    private PropBindingFactory newPropBindingFactory()
+    {
+        return new PropBindingFactory(new PropertyAccessImpl(), new ClassFactoryImpl());
+    }
+
+    private ComponentResources newComponentResources(ComponentLifecycle component)
+    {
+        ComponentResources resources = newComponentResources();
+        train_getComponent(resources, component);
+
+        train_getCompleteId(resources, "foo.Bar:baz");
+
+        return resources;
+    }
+
+    protected void train_getCompleteId(ComponentResources resources, String completeId)
+    {
+        resources.getCompleteId();
+        setReturnValue(completeId);
+    }
+
+    private void train_getComponent(ComponentResources resources, ComponentLifecycle component)
+    {
+        resources.getComponent();
+        setReturnValue(component);
+    }
+
+    @Test
+    public void object_property()
+    {
+        PropBindingFactory factory = newPropBindingFactory();
+        TargetBean bean = new TargetBean();
+        ComponentResources resources = newComponentResources(bean);
+        Location l = newLocation();
+
+        replay();
+
+        Binding binding = factory.newBinding("test binding", resources, "objectValue", l);
+
+        bean.setObjectValue("first");
+
+        assertEquals(binding.get(), "first");
+
+        binding.set("second");
+
+        assertEquals(bean.getObjectValue(), "second");
+        assertEquals(IOCUtilities.locationOf(binding), l);
+
+        assertEquals(binding.toString(), "PropBinding[test binding foo.Bar:baz(objectValue)]");
+
+        verify();
+    }
+
+    @Test
+    public void primitive_property()
+    {
+        PropBindingFactory factory = newPropBindingFactory();
+        TargetBean bean = new TargetBean();
+        ComponentResources resources = newComponentResources(bean);
+        Location l = newLocation();
+
+        replay();
+
+        Binding binding = factory.newBinding("test binding", resources, "intValue", l);
+
+        bean.setIntValue(1);
+
+        assertEquals(binding.get(), 1);
+
+        binding.set(2);
+
+        assertEquals(bean.getIntValue(), 2);
+
+        verify();
+    }
+
+    @Test
+    public void read_only_property()
+    {
+        PropBindingFactory factory = newPropBindingFactory();
+        TargetBean bean = new TargetBean();
+        ComponentResources resources = newComponentResources(bean);
+        Location l = newLocation();
+
+        replay();
+
+        Binding binding = factory.newBinding("test binding", resources, "readOnly", l);
+
+        assertEquals(binding.get(), "ReadOnly");
+
+        try
+        {
+            binding.set("fail");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    "Binding PropBinding[test binding foo.Bar:baz(readOnly)] is read-only.",
+                    ex.getMessage());
+            assertEquals(IOCUtilities.locationOf(ex), l);
+        }
+
+        verify();
+    }
+
+    @Test
+    public void write_only_property()
+    {
+        PropBindingFactory factory = newPropBindingFactory();
+        TargetBean bean = new TargetBean();
+        ComponentResources resources = newComponentResources(bean);
+        Location l = newLocation();
+
+        replay();
+
+        Binding binding = factory.newBinding("test binding", resources, "writeOnly", l);
+
+        binding.set("updated");
+
+        assertEquals(bean._writeOnly, "updated");
+
+        try
+        {
+            assertEquals(binding.get(), "ReadOnly");
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(
+                    "Binding PropBinding[test binding foo.Bar:baz(writeOnly)] is write-only.",
+                    ex.getMessage());
+            assertEquals(IOCUtilities.locationOf(ex), l);
+        }
+
+        verify();
+    }
+
+    @Test
+    public void caching()
+    {
+        PropBindingFactory factory = newPropBindingFactory();
+        TargetBean bean1 = new TargetBean();
+        ComponentResources resources1 = newComponentResources(bean1);
+        TargetBean bean2 = new TargetBean();
+        ComponentResources resources2 = newComponentResources(bean2);
+        TargetBean bean3 = new TargetBean();
+        ComponentResources resources3 = newComponentResources(bean3);
+
+        Location l = newLocation();
+
+        replay();
+
+        Binding binding1 = factory.newBinding("test binding 1", resources1, "objectValue", l);
+        Binding binding2 = factory.newBinding("test binding 2", resources2, "objectValue", l);
+
+        assertSame(binding2.getClass(), binding1.getClass());
+
+        binding1.set("foo");
+        binding2.set("bar");
+
+        assertEquals(bean1.getObjectValue(), "foo");
+        assertEquals(bean2.getObjectValue(), "bar");
+
+        // Now, clear the cache.
+
+        factory.objectWasInvalidated(new InvalidationEvent(this));
+
+        Binding binding3 = factory.newBinding("test binding 3", resources3, "objectValue", l);
+
+        // We'll assume the behavior is the same, but expect the class to be different.
+
+        assertNotSame(binding3.getClass(), binding1.getClass());
+
+        verify();
+    }
+
+    @Test
+    public void unknown_property()
+    {
+        PropBindingFactory factory = newPropBindingFactory();
+        TargetBean bean = new TargetBean();
+        ComponentResources resources = newComponentResources();
+        Location l = newLocation();
+
+        train_getComponent(resources, bean);
+
+        replay();
+
+        try
+        {
+            factory.newBinding("test binding", resources, "missingProperty", l);
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            assertEquals(ex.getMessage(), String.format(
+                    "Class %s does not contain a property named 'missingProperty'.",
+                    bean.getClass().getName()));
+            assertSame(IOCUtilities.locationOf(ex), l);
+        }
+
+        verify();
+    }
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/TargetBean.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/TargetBean.java?view=auto&rev=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/TargetBean.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/TargetBean.java Mon Sep  4 10:34:27 2006
@@ -0,0 +1,40 @@
+package org.apache.tapestry.internal.bindings;
+
+public class TargetBean extends DefaultComponentLifecyle
+{
+    private String _objectValue;
+
+    private int _intValue;
+    
+    String _writeOnly;
+
+    public int getIntValue()
+    {
+        return _intValue;
+    }
+
+    public void setIntValue(int intValue)
+    {
+        _intValue = intValue;
+    }
+
+    public String getObjectValue()
+    {
+        return _objectValue;
+    }
+
+    public void setObjectValue(String objectValue)
+    {
+        _objectValue = objectValue;
+    }
+
+    public void setWriteOnly(String value)
+    {
+        _writeOnly = value;
+    }
+
+    public String getReadOnly()
+    {
+        return "ReadOnly";
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ClassFabImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ClassFabImplTest.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ClassFabImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/ClassFabImplTest.java Mon Sep  4 10:34:27 2006
@@ -61,7 +61,7 @@
 
         ClassFactoryClassPool pool = new ClassFactoryClassPool(threadLoader);
 
-        pool.appendClassLoader(threadLoader);
+        pool.addClassLoaderIfNeeded(threadLoader);
 
         _source = new CtClassSource(pool);
     }
@@ -375,22 +375,25 @@
         // You'd think some of these would fail, but the ultimate failure
         // occurs when we create the class.
 
-        cf.addField(".", String.class);
-        cf.addField("buffy", int.class);
+        cf.addField("a%b", String.class);
         cf.addField("", int.class);
 
+        // Aha! Adding a duplicate fails!
+
+        cf.addField("buffy", int.class);
+
         try
         {
-            cf.createClass();
+            cf.addField("buffy", String.class);
             unreachable();
         }
         catch (RuntimeException ex)
         {
-            assertExceptionSubstring(ex, "Unable to create class InvalidField");
+            assertEquals(
+                    ex.getMessage(),
+                    "Unable to add field buffy to class InvalidField: duplicate field: buffy");
         }
 
-        // Javassist lets us down here; I can't think of a way to get addField() to actually
-        // fail.
     }
 
     @Test

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/UpdateListenerHubImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/UpdateListenerHubImplTest.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/UpdateListenerHubImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/UpdateListenerHubImplTest.java Mon Sep  4 10:34:27 2006
@@ -42,13 +42,5 @@
         hub.fireUpdateEvent();
 
         verify();
-
-        hub.removeUpdateListener(listener);
-
-        replay();
-
-        hub.fireUpdateEvent();
-
-        verify();
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/ClassFabUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/ClassFabUtilsTest.java?view=diff&rev=440132&r1=440131&r2=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/ClassFabUtilsTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/ioc/services/ClassFabUtilsTest.java Mon Sep  4 10:34:27 2006
@@ -44,4 +44,16 @@
         { "byte[][]", "[[B" },
         { "java.lang.Runnable[][]", "[[Ljava.lang.Runnable;" } };
     }
+
+    @Test
+    public void unwrap_method()
+    {
+        assertEquals(ClassFabUtils.getUnwrapMethodName("int"), "intValue");
+    }
+
+    @Test
+    public void wrapper_type()
+    {
+        assertEquals(ClassFabUtils.getWrapperType("int"), "java.lang.Integer");
+    }
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/MerryChristmas.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/MerryChristmas.html?view=auto&rev=440132
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/MerryChristmas.html (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/MerryChristmas.html Mon Sep  4 10:34:27 2006
@@ -0,0 +1,11 @@
+<t:comp id="border" type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    <p> The Loop component demonstrates reading and writing of properties with the prop: binding
+        prefix.</p>
+        
+    <p>
+        Merry Christmas:
+        <t:comp id="loop" type="Loop" min="prop:min" max="prop:max" value="prop:value">
+            Ho!
+        </t:comp>
+    </p>
+</t:comp>