You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2006/10/26 18:30:11 UTC

svn commit: r468058 - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/annotations/ main/java/org/apache/tapestry/internal/ main/java/org/apache/tapestry/internal/model/ main/java/org/apache/tapestry/internal/services/ main...

Author: hlship
Date: Thu Oct 26 09:30:09 2006
New Revision: 468058

URL: http://svn.apache.org/viewvc?view=rev&rev=468058
Log:
Initial support for implementation mixins

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Mixin.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MixinWorker.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/FormSupport.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/MixinWorkerTest.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalComponentResourcesCommon.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/model/MutableComponentModelImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.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/ServicesMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.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/internal/structure/InternalComponentResourcesImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/ComponentModel.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/MutableComponentModel.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.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/BaseTestCase.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/mixins.apt
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Mixin.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Mixin.java?view=auto&rev=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Mixin.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Mixin.java Thu Oct 26 09:30:09 2006
@@ -0,0 +1,26 @@
+package org.apache.tapestry.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Defines an <em>implementation</em> mixin for a comopnent.
+ */
+@Target(FIELD)
+@Documented
+@Retention(RUNTIME)
+public @interface Mixin {
+
+    /**
+     * Defines the type of mixin, using a logical mixin name. This value takes precendence over the
+     * type of field (to which the annotation is attached). In such cases, it is presumed that the
+     * field's type is an interface implemented by the actual mixin. The default value (the empty
+     * string) directs Tapestry to use the field type as the mixin class to instantiate and attach
+     * to the component.
+     */
+    String value() default "";
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalComponentResourcesCommon.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalComponentResourcesCommon.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalComponentResourcesCommon.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/InternalComponentResourcesCommon.java Thu Oct 26 09:30:09 2006
@@ -17,10 +17,13 @@
 import org.apache.tapestry.Binding;
 import org.apache.tapestry.internal.structure.ComponentPageElement;
 import org.apache.tapestry.internal.structure.Page;
+import org.apache.tapestry.runtime.ComponentLifecycle;
 import org.apache.tapestry.services.PersistentFieldManager;
 
 /**
  * Operations shared by {@link InternalComponentResources} and {@link ComponentPageElement}.
+ * Typically, these means methods of InternalComponentResources that are delegated to the component
+ * page element.
  */
 public interface InternalComponentResourcesCommon
 {
@@ -57,4 +60,13 @@
      * TODO: Would prefer to move this to a sub-interface, say "MutableInternalComponentResources".
      */
     void addParameter(String parameterName, Binding binding);
+
+    /**
+     * Returns the mixin instance for the fully qualfied mixin class name.
+     * 
+     * @param mixinClassName
+     *            fully qualified class name
+     * @return IllegalArgumentException if no such mixin is associated with the core component
+     */
+    ComponentLifecycle getMixinByClassName(String mixinClassName);
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/model/MutableComponentModelImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/model/MutableComponentModelImpl.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/model/MutableComponentModelImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/model/MutableComponentModelImpl.java Thu Oct 26 09:30:09 2006
@@ -55,6 +55,8 @@
     /** Maps from field name to strategy. */
     private Map<String, String> _persistentFields;
 
+    private List<String> _mixinClassNames;
+
     @SuppressNullCheck
     public MutableComponentModelImpl(String componentClassName, Log log, Resource baseResource,
             ComponentModel parentModel)
@@ -224,4 +226,21 @@
     {
         return _parentModel == null;
     }
+
+    public void addMixinClassName(String mixinClassName)
+    {
+        if (_mixinClassNames == null)
+            _mixinClassNames = newList();
+
+        _mixinClassNames.add(mixinClassName);
+    }
+
+    public List<String> getMixinClassNames()
+    {
+        if (_mixinClassNames == null)
+            return Collections.emptyList();
+
+        return Collections.unmodifiableList(_mixinClassNames);
+    }
+
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentClassResolverImpl.java Thu Oct 26 09:30:09 2006
@@ -103,17 +103,32 @@
 
     public String resolvePageNameToClassName(String pageName)
     {
-        return resolve(pageName, "pages", _pageClassCache);
+        String result = resolve(pageName, "pages", _pageClassCache);
+
+        if (result == null)
+            throw new IllegalArgumentException(ServicesMessages.couldNotResolvePageName(pageName));
+
+        return result;
     }
 
     public String resolveComponentTypeToClassName(String componentType)
     {
-        return resolve(componentType, "components", _componentClassCache);
+        String result = resolve(componentType, "components", _componentClassCache);
+
+        if (result == null)
+            throw new IllegalArgumentException(ServicesMessages.couldNotResolveComponentType(componentType));
+
+        return result;
     }
 
     public String resolveMixinTypeToClassName(String mixinType)
     {
-        return resolve(mixinType, "mixins", _mixinClassCache);
+        String result = resolve(mixinType, "mixins", _mixinClassCache);
+
+        if (result == null)
+            throw new IllegalArgumentException(ServicesMessages.couldNotResolveMixinType(mixinType));
+
+        return result;
     }
 
     // Used a thread-safe (concurrent) map rather than making this method itself

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MixinWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MixinWorker.java?view=auto&rev=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MixinWorker.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/MixinWorker.java Thu Oct 26 09:30:09 2006
@@ -0,0 +1,59 @@
+package org.apache.tapestry.internal.services;
+
+import java.util.List;
+
+import org.apache.tapestry.annotations.Mixin;
+import org.apache.tapestry.internal.util.InternalUtils;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.ComponentClassResolver;
+import org.apache.tapestry.services.ComponentClassTransformWorker;
+import org.apache.tapestry.services.TransformConstants;
+
+/**
+ * Supports the {@link Mixin} annotation, which allows a mixin to be part of the implementation of a
+ * component. The annotation is applied to a field, which will become read-only, and contain a
+ * reference to the mixin instance.
+ */
+public class MixinWorker implements ComponentClassTransformWorker
+{
+    private final ComponentClassResolver _resolver;
+
+    public MixinWorker(final ComponentClassResolver resolver)
+    {
+        _resolver = resolver;
+    }
+
+    public void transform(ClassTransformation transformation, MutableComponentModel model)
+    {
+        List<String> fields = transformation.findFieldsWithAnnotation(Mixin.class);
+
+        for (String fieldName : fields)
+        {
+            Mixin annotation = transformation.getFieldAnnotation(fieldName, Mixin.class);
+
+            String mixinType = annotation.value();
+
+            String fieldType = transformation.getFieldType(fieldName);
+
+            String mixinClassName = InternalUtils.isBlank(mixinType) ? fieldType : _resolver
+                    .resolveMixinTypeToClassName(mixinType);
+
+            model.addMixinClassName(mixinClassName);
+
+            transformation.makeReadOnly(fieldName);
+
+            String body = String.format(
+                    "%s = (%s) %s.getMixinByClassName(\"%s\");",
+                    fieldName,
+                    fieldType,
+                    transformation.getResourcesFieldName(),
+                    mixinClassName);
+
+            transformation
+                    .extendMethod(TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE, body);
+
+            transformation.claimField(fieldName, annotation);
+        }
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageElementFactory.java Thu Oct 26 09:30:09 2006
@@ -65,7 +65,7 @@
             String componentType, String componentClassName, Location location);
 
     /**
-     * Creates a new root component for a page.
+     * Creates a new root component for a page. Adds any mixins defined by the components model.
      * 
      * @param page
      *            the page that will contain the root component

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=468058&r1=468057&r2=468058
==============================================================================
--- 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 Oct 26 09:30:09 2006
@@ -34,8 +34,8 @@
 import org.apache.tapestry.internal.structure.StartElementPageElement;
 import org.apache.tapestry.internal.structure.TextPageElement;
 import org.apache.tapestry.internal.util.InternalUtils;
-import org.apache.tapestry.ioc.IOCUtilities;
 import org.apache.tapestry.ioc.services.TypeCoercer;
+import org.apache.tapestry.model.ComponentModel;
 import org.apache.tapestry.runtime.RenderQueue;
 import org.apache.tapestry.services.BindingSource;
 import org.apache.tapestry.services.ComponentClassResolver;
@@ -126,11 +126,15 @@
             // by the type of the field. In many scenarios, the field type is a common interface,
             // and the type is used to determine the concrete class to instantiate.
 
-            finalClassName = _componentClassResolver.resolveComponentTypeToClassName(componentType);
-
-            if (finalClassName == null)
-                throw new TapestryException(ServicesMessages
-                        .unableToResolveComponentType(componentType), location, null);
+            try
+            {
+                finalClassName = _componentClassResolver
+                        .resolveComponentTypeToClassName(componentType);
+            }
+            catch (IllegalArgumentException ex)
+            {
+                throw new TapestryException(ex.getMessage(), location, ex);
+            }
         }
 
         Instantiator instantiator = _componentInstantiatorSource.findInstantiator(finalClassName);
@@ -146,6 +150,8 @@
 
         container.addEmbeddedElement(result);
 
+        addMixins(result, instantiator);
+
         return result;
     }
 
@@ -156,11 +162,20 @@
         ComponentPageElementImpl result = new ComponentPageElementImpl(page, instantiator,
                 _typeCoercer);
 
+        addMixins(result, instantiator);
+
         page.addLifecycleListener(result);
 
         return result;
     }
 
+    private void addMixins(ComponentPageElement component, Instantiator instantiator)
+    {
+        ComponentModel model = instantiator.getModel();
+        for (String mixinClassName : model.getMixinClassNames())
+            addMixinByClassName(component, mixinClassName);
+    }
+
     public PageElement newRenderBodyElement(final ComponentPageElement component)
     {
         return new PageElement()
@@ -191,9 +206,7 @@
         Instantiator mixinInstantiator = _componentInstantiatorSource
                 .findInstantiator(mixinClassName);
 
-        String mixinName = IOCUtilities.toSimpleId(mixinClassName);
-
-        component.addMixin(mixinName, mixinInstantiator);
+        component.addMixin(mixinInstantiator);
     }
 
 }

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=468058&r1=468057&r2=468058
==============================================================================
--- 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 Oct 26 09:30:09 2006
@@ -366,6 +366,14 @@
         }
     }
 
+    private void addMixinsToComponent(ComponentPageElement component, ComponentModel model)
+    {
+        for (String mixinClassName : model.getMixinClassNames())
+        {
+            _pageElementFactory.addMixinByClassName(component, mixinClassName);
+        }
+    }
+
     private void addMixinsToComponent(ComponentPageElement component, EmbeddedComponentModel model,
             String mixins)
     {

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java Thu Oct 26 09:30:09 2006
@@ -183,11 +183,6 @@
         return MESSAGES.format("context-index-out-of-range", methodDescription);
     }
 
-    static String unableToResolveComponentType(String componentType)
-    {
-        return MESSAGES.format("unable-to-resolve-component-type", componentType);
-    }
-
     static String pageDoesNotExist(String pageName)
     {
         return MESSAGES.format("page-does-not-exist", pageName);
@@ -212,5 +207,20 @@
     static String unknownPersistentFieldStrategy(String stategyName, String catalog)
     {
         return MESSAGES.format("unknown-persistent-field-strategy", stategyName, catalog);
+    }
+
+    static String couldNotResolvePageName(String pageName)
+    {
+        return MESSAGES.format("could-not-resolve-page-name", pageName);
+    }
+
+    static String couldNotResolveComponentType(String componentType)
+    {
+        return MESSAGES.format("could-not-resolve-component-type", componentType);
+    }
+
+    static String couldNotResolveMixinType(String mixinType)
+    {
+        return MESSAGES.format("could-not-resolve-mixin-type", mixinType);
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java Thu Oct 26 09:30:09 2006
@@ -73,13 +73,10 @@
     /**
      * Adds a mixin.
      * 
-     * @param mixinName
-     *            the logical name of the mixin, which consists of just the class name portion of
-     *            the mixin's fully qualified class name (and will be unique for a single component)
      * @param instantiator
      *            used to instantiate an instance of the mixin
      */
-    void addMixin(String mixinName, Instantiator instantiator);
+    void addMixin(Instantiator instantiator);
 
     /**
      * Retrieves a component page element by its id.

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=468058&r1=468057&r2=468058
==============================================================================
--- 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 Thu Oct 26 09:30:09 2006
@@ -34,6 +34,7 @@
 import org.apache.tapestry.internal.services.ComponentEventImpl;
 import org.apache.tapestry.internal.services.Instantiator;
 import org.apache.tapestry.internal.util.InternalUtils;
+import org.apache.tapestry.ioc.IOCUtilities;
 import org.apache.tapestry.ioc.services.TypeCoercer;
 import org.apache.tapestry.model.ComponentModel;
 import org.apache.tapestry.model.ParameterModel;
@@ -93,7 +94,7 @@
     private final TypeCoercer _typeCoercer;
 
     /** Map from mixin name to resources for the mixin. */
-    private Map<String, InternalComponentResources> _mixins;
+    private Map<String, InternalComponentResources> _mixinsByShortName;
 
     /**
      * Component lifecycle instances for all mixins; the core component is added to this list during
@@ -211,7 +212,9 @@
         if (dotx > 0)
         {
             String mixinName = parameterName.substring(0, dotx);
-            InternalComponentResources mixinResources = InternalUtils.get(_mixins, mixinName);
+            InternalComponentResources mixinResources = InternalUtils.get(
+                    _mixinsByShortName,
+                    mixinName);
 
             if (mixinResources == null)
                 throw new TapestryException(StructureMessages.missingMixinForParameter(
@@ -219,7 +222,7 @@
                         mixinName,
                         parameterName), binding, null);
 
-            String simpleName = mixinName.substring(dotx + 1);
+            String simpleName = parameterName.substring(dotx + 1);
 
             mixinResources.addParameter(simpleName, binding);
         }
@@ -232,9 +235,9 @@
             return;
         }
 
-        for (String mixinName : InternalUtils.sortedKeys(_mixins))
+        for (String mixinName : InternalUtils.sortedKeys(_mixinsByShortName))
         {
-            InternalComponentResources resources = _mixins.get(mixinName);
+            InternalComponentResources resources = _mixinsByShortName.get(mixinName);
             if (resources.getComponentModel().getParameterModel(parameterName) != null)
             {
                 resources.addParameter(parameterName, binding);
@@ -512,8 +515,8 @@
 
         addUnboundParameterNames(null, unbound, _coreResources);
 
-        for (String name : InternalUtils.sortedKeys(_mixins))
-            addUnboundParameterNames(name, unbound, _mixins.get(name));
+        for (String name : InternalUtils.sortedKeys(_mixinsByShortName))
+            addUnboundParameterNames(name, unbound, _mixinsByShortName.get(name));
 
         if (unbound.isEmpty())
             return;
@@ -618,17 +621,20 @@
         return getFieldChange(fieldName) != null;
     }
 
-    public void addMixin(String mixinName, Instantiator instantiator)
+    public void addMixin(Instantiator instantiator)
     {
-        if (_mixins == null)
-            _mixins = newMap();
+        if (_mixinsByShortName == null)
+            _mixinsByShortName = newMap();
+
+        String mixinClassName = instantiator.getModel().getComponentClassName();
+        String mixinName = IOCUtilities.toSimpleId(mixinClassName);
 
         InternalComponentResourcesImpl resources = new InternalComponentResourcesImpl(this,
                 instantiator, _typeCoercer);
 
         // TODO: Check for name collision?
 
-        _mixins.put(mixinName, resources);
+        _mixinsByShortName.put(mixinName, resources);
         _components.add(resources.getComponent());
     }
 
@@ -641,4 +647,29 @@
     {
         return _coreResources.getLog();
     }
+
+    public ComponentLifecycle getMixinByClassName(String mixinClassName)
+    {
+        ComponentLifecycle result = null;
+
+        if (_mixinsByShortName != null)
+        {
+            for (InternalComponentResources resources : _mixinsByShortName.values())
+            {
+                if (resources.getComponentModel().getComponentClassName().equals(mixinClassName))
+                {
+                    result = resources.getComponent();
+                    break;
+                }
+            }
+        }
+
+        if (result == null)
+            throw new IllegalArgumentException(StructureMessages.unknownMixin(
+                    _completeId,
+                    mixinClassName));
+
+        return result;
+    }
+
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/InternalComponentResourcesImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/InternalComponentResourcesImpl.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/InternalComponentResourcesImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/InternalComponentResourcesImpl.java Thu Oct 26 09:30:09 2006
@@ -214,4 +214,9 @@
         return _componentModel.getLog();
     }
 
+    public ComponentLifecycle getMixinByClassName(String mixinClassName)
+    {
+        return _element.getMixinByClassName(mixinClassName);
+    }
+
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java Thu Oct 26 09:30:09 2006
@@ -53,4 +53,9 @@
         return MESSAGES
                 .format("missing-mixin-for-parameter", componentId, mixinName, parameterName);
     }
+
+    static String unknownMixin(String componentId, String mixinClassName)
+    {
+        return MESSAGES.format("unknown-mixin", componentId, mixinClassName);
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/ComponentModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/ComponentModel.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/ComponentModel.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/ComponentModel.java Thu Oct 26 09:30:09 2006
@@ -92,4 +92,7 @@
      * @return true if a root class, false if a subclass
      */
     boolean isRootClass();
+
+    /** Returns a list of the class names of mixins that are part of the component's implementation. */
+    List<String> getMixinClassNames();
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/MutableComponentModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/MutableComponentModel.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/MutableComponentModel.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/model/MutableComponentModel.java Thu Oct 26 09:30:09 2006
@@ -64,4 +64,7 @@
      *         {@link InternalComponentResources#postFieldChange(String, Object)}, etc.
      */
     String setFieldPersistenceStrategy(String fieldName, String strategy);
+
+    /** Adds a mixin to the component's implementation. */
+    void addMixinClassName(String mixinClassName);
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ComponentClassResolver.java Thu Oct 26 09:30:09 2006
@@ -86,7 +86,9 @@
      * 
      * @param componentType
      *            a logical component type
-     * @return fully qualified class name or null if the component type can not be resolved
+     * @return fully qualified class name
+     * @throws IllegalArgumentException
+     *             if the component type can not be resolved
      */
     String resolveComponentTypeToClassName(String componentType);
 
@@ -95,7 +97,9 @@
      * 
      * @param mixinType
      *            a logical mixin type
-     * @return fully qualified class name or null if the mixin type can not be resolved
+     * @return fully qualified class name
+     * @throws IllegalArgumentException
+     *             if the mixin type can not be resolved
      */
     String resolveMixinTypeToClassName(String mixinType);
 

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/FormSupport.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/FormSupport.java?view=auto&rev=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/FormSupport.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/FormSupport.java Thu Oct 26 09:30:09 2006
@@ -0,0 +1,12 @@
+package org.apache.tapestry.services;
+
+/**
+ * Services provided by an enclosing Form control component to the various form element components
+ * it encloses.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public interface FormSupport
+{
+
+}

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=468058&r1=468057&r2=468058
==============================================================================
--- 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 Oct 26 09:30:09 2006
@@ -53,6 +53,7 @@
 import org.apache.tapestry.internal.services.InternalModule;
 import org.apache.tapestry.internal.services.LinkFactory;
 import org.apache.tapestry.internal.services.MarkupWriterImpl;
+import org.apache.tapestry.internal.services.MixinWorker;
 import org.apache.tapestry.internal.services.OnEventWorker;
 import org.apache.tapestry.internal.services.PageRenderDispatcher;
 import org.apache.tapestry.internal.services.PageResponseRenderer;
@@ -480,6 +481,7 @@
      * {@link org.apache.tapestry.annotations.Parameter} annotation</li>
      * <li>Component -- identifies embedded components based on the
      * {@link org.apache.tapestry.annotations.Component} annotation</li>
+     * <li>Mixin -- adds a mixin as part of a component's implementation</li>
      * <li>Environment -- allows fields to contain values extracted from the {@link Environment}
      * service</li>
      * <li>UnclaimedField -- identifies unclaimed fields and resets them to null/0/false at the end
@@ -503,7 +505,7 @@
         configuration.add("Parameter", new ParameterWorker());
         configuration.add("Component", new ComponentWorker(resolver));
         configuration.add("Environment", new EnvironmentalWorker(environment));
-
+        configuration.add("Mixin", new MixinWorker(resolver));
         configuration.add("OnEvent", new OnEventWorker());
 
         // Workers for the component rendering state machine methods; this is in typical
@@ -511,10 +513,14 @@
 
         add(configuration, TransformConstants.SETUP_RENDER_SIGNATURE, SetupRender.class, false);
         add(configuration, TransformConstants.BEGIN_RENDER_SIGNATURE, BeginRender.class, false);
-        add(configuration, TransformConstants.BEFORE_RENDER_BODY_SIGNATURE, BeforeRenderBody.class, false);
-        
+        add(
+                configuration,
+                TransformConstants.BEFORE_RENDER_BODY_SIGNATURE,
+                BeforeRenderBody.class,
+                false);
+
         // These phases operate in reverse order.
-        
+
         add(configuration, TransformConstants.AFTER_RENDER_SIGNATURE, AfterRender.class, true);
         add(configuration, TransformConstants.CLEANUP_RENDER_SIGNATURE, CleanupRender.class, true);
 
@@ -530,7 +536,8 @@
 
         String name = IOCUtilities.toSimpleId(annotationClass.getName());
 
-        configuration.add(name, new ComponentLifecycleMethodWorker(signature, annotationClass, reverse));
+        configuration.add(name, new ComponentLifecycleMethodWorker(signature, annotationClass,
+                reverse));
     }
 
     @Lifecycle("perthread")

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/BaseTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/BaseTestCase.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/BaseTestCase.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/BaseTestCase.java Thu Oct 26 09:30:09 2006
@@ -480,7 +480,7 @@
     protected final void train_getComponentClassName(ComponentModel model, String className)
     {
         model.getComponentClassName();
-        setReturnValue(className);
+        setReturnValue(className).anyTimes();
     }
 
     protected final void train_isRequired(ParameterModel model, boolean isRequired)

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties Thu Oct 26 09:30:09 2006
@@ -47,10 +47,12 @@
   or define the component inside class %s using the @Component annotation on a private instance variable.
 embedded-components-not-in-template=Embedded component(s) %s are defined within component class %s, but are not present in the component template.
 binding-source-failure=Could not convert '%s' into a component parameter binding: %s
-unable-to-resolve-component-type=Unable to resolve component '%s' to a component class name.
 page-does-not-exist=Page '%s' is not defined by this application.
 page-name-unresolved=Unable to resolve class name %s to a logical page name.
 context-index-out-of-range=Method %s has more parameters than there are context values for this component event.
 exception-in-method-parameter=Exception in method %s, parameter #%d: %s
 component-event-is-aborted=Can not store result from invoking method %s, because an event result value has already been obtained from some other event handler method.
-unknown-persistent-field-strategy='%s' is not a defined persistent strategy.  Defined stategies: %s.
\ No newline at end of file
+unknown-persistent-field-strategy='%s' is not a defined persistent strategy.  Defined stategies: %s.
+could-not-resolve-page-name=Unable to resolve page '%s' to a component class name.
+could-not-resolve-component-type=Unable to resolve component type '%s' to a component class name.
+could-not-resolve-mixin-type=Unable to resolve mixin type '%s' to a component class name.
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties Thu Oct 26 09:30:09 2006
@@ -16,4 +16,5 @@
 no-such-component=Component %s does not contain an embedded component with id '%s'.
 get-parameter-failure=Failure reading parameter %s of component %s: %s
 write-parameter-failure=Failure writing parameter %s of component %s: %s
-missing-mixin-for-parameter=Component %s does not contain a mixin named '%s' (setting parameter '%s').
\ No newline at end of file
+missing-mixin-for-parameter=Component %s does not contain a mixin named '%s' (setting parameter '%s').
+unknown-mixin=Component %s does not contain a mixin of type %s.
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/mixins.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/mixins.apt?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/mixins.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/mixins.apt Thu Oct 26 09:30:09 2006
@@ -68,6 +68,40 @@
   This example defines a component of type TextField and mixes in the <hypothetical> Autocomplete
   and DefaultFromCookie mixins.  
 
+
+Implementation Mixins
+
+  Implementation mixins, mixins which apply to all isntances of a component, are added using the
+  {{{../apidocs/org/apache/tapestry/annotations/Mixin.html}Mixin annotation}}. This annotation
+  defines a field that will containg the mixin instance.
+  
++---+
+@ComponentClass
+public class AutocompleteField extendes TextField
+{
+  @Mixin
+  private Autocomplete _autocompleteMixin;
+  
+  . . .
+}
++---+
+
+  Often, the type of the field is the exact mixin class to be instantiated.
+  
+  In other cases, such as when the field's type is an interface or a base class, the value
+  attribute of the annotation will be used to determine the mixin class name:
+  
++---+
+@ComponentClass
+public class AutocompleteField extendes TextField
+{
+  @Mixin("Autocomplete")
+  private Object _autocompleteMixin;
+  
+  . . .
+}
++---+
+
 Mixin Parameters
 
   Mixins are allowed to have parameters, just like components.
@@ -89,9 +123,6 @@
   private TextField _userId;
 +-----+
 
-Implementation Mixins
-
-  Not yet implemented.
   
 Render Phase Ordering
 

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentClassResolverImplTest.java Thu Oct 26 09:30:09 2006
@@ -348,7 +348,17 @@
                 new LibraryMapping(LIB_PREFIX, LIB_ROOT_PACKAGE),
                 new LibraryMapping(CORE_PREFIX, CORE_ROOT_PACKAGE));
 
-        assertNull(resolver.resolvePageNameToClassName("lib/deep/DeepPage"));
+        try
+        {
+            resolver.resolvePageNameToClassName("lib/deep/DeepPage");
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Unable to resolve page 'lib/deep/DeepPage' to a component class name.");
+        }
 
         verify();
     }
@@ -371,7 +381,17 @@
         ComponentClassResolver resolver = create(source, new LibraryMapping(LIB_PREFIX,
                 LIB_ROOT_PACKAGE), new LibraryMapping(CORE_PREFIX, CORE_ROOT_PACKAGE));
 
-        assertNull(resolver.resolvePageNameToClassName("lib/MissingPage"));
+        try
+        {
+            resolver.resolvePageNameToClassName("lib/MissingPage");
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Unable to resolve page 'lib/MissingPage' to a component class name.");
+        }
 
         verify();
     }
@@ -427,9 +447,69 @@
 
         ComponentClassResolver resolver = create(source);
 
+        resolver.setApplicationPackage(APP_ROOT_PACKAGE);
+
         assertEquals(resolver.resolveMixinTypeToClassName("SimpleMixin"), expectedClassName);
 
         verify();
+    }
+
+    @Test
+    public void mixin_type_not_found()
+    {
+        ComponentInstantiatorSource source = newComponentInstantiatorSource();
+
+        train_for_packages(source, CORE_ROOT_PACKAGE);
+        train_for_app_packages(source);
+
+        replay();
+
+        ComponentClassResolver resolver = create(source, new LibraryMapping(CORE_PREFIX,
+                CORE_ROOT_PACKAGE));
+
+        try
+        {
+            resolver.resolveMixinTypeToClassName("SimpleMixin");
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Unable to resolve mixin type 'SimpleMixin' to a component class name.");
+        }
+
+        verify();
+
+    }
+
+    @Test
+    public void component_type_not_found()
+    {
+        ComponentInstantiatorSource source = newComponentInstantiatorSource();
+
+        train_for_packages(source, CORE_ROOT_PACKAGE);
+        train_for_app_packages(source);
+
+        replay();
+
+        ComponentClassResolver resolver = create(source, new LibraryMapping(CORE_PREFIX,
+                CORE_ROOT_PACKAGE));
+
+        try
+        {
+            resolver.resolveComponentTypeToClassName("SimpleComponent");
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Unable to resolve component type 'SimpleComponent' to a component class name.");
+        }
+
+        verify();
+
     }
 
     @Test

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/MixinWorkerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/MixinWorkerTest.java?view=auto&rev=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/MixinWorkerTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/MixinWorkerTest.java Thu Oct 26 09:30:09 2006
@@ -0,0 +1,108 @@
+package org.apache.tapestry.internal.services;
+
+import org.apache.tapestry.annotations.Mixin;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.ComponentClassResolver;
+import org.apache.tapestry.services.TransformConstants;
+import org.testng.annotations.Test;
+
+public class MixinWorkerTest extends InternalBaseTestCase
+{
+    @Test
+    public void no_fields_with_mixin_annotation()
+    {
+        ComponentClassResolver resolver = newComponentClassResolver();
+        ClassTransformation transformation = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+
+        train_findFieldsWithAnnotation(transformation, Mixin.class);
+
+        replay();
+
+        new MixinWorker(resolver).transform(transformation, model);
+
+        verify();
+    }
+
+    @Test
+    public void field_with_explicit_type()
+    {
+        ComponentClassResolver resolver = newComponentClassResolver();
+        ClassTransformation transformation = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        Mixin annotation = newMixin("Bar");
+
+        train_findFieldsWithAnnotation(transformation, Mixin.class, "fred");
+        train_getFieldAnnotation(transformation, "fred", Mixin.class, annotation);
+        train_getFieldType(transformation, "fred", "foo.bar.Baz");
+
+        train_resolveMixinTypeToClassName(resolver, "Bar", "foo.bar.BazMixin");
+
+        model.addMixinClassName("foo.bar.BazMixin");
+
+        transformation.makeReadOnly("fred");
+
+        train_getResourcesFieldName(transformation, "rez");
+
+        train_extendMethod(
+                transformation,
+                TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE,
+                "fred = (foo.bar.Baz) rez.getMixinByClassName(\"foo.bar.BazMixin\");");
+
+        replay();
+
+        new MixinWorker(resolver).transform(transformation, model);
+
+        verify();
+    }
+
+    @Test
+    public void field_with_no_specific_mixin_type()
+    {
+        ComponentClassResolver resolver = newComponentClassResolver();
+        ClassTransformation transformation = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        Mixin annotation = newMixin("");
+
+        train_findFieldsWithAnnotation(transformation, Mixin.class, "fred");
+        train_getFieldAnnotation(transformation, "fred", Mixin.class, annotation);
+        train_getFieldType(transformation, "fred", "foo.bar.Baz");
+
+        model.addMixinClassName("foo.bar.Baz");
+
+        transformation.makeReadOnly("fred");
+
+        train_getResourcesFieldName(transformation, "rez");
+
+        train_extendMethod(
+                transformation,
+                TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE,
+                "fred = (foo.bar.Baz) rez.getMixinByClassName(\"foo.bar.Baz\");");
+
+        replay();
+
+        new MixinWorker(resolver).transform(transformation, model);
+
+        verify();
+
+    }
+
+    protected final void train_resolveMixinTypeToClassName(ComponentClassResolver resolver,
+            String mixinType, String mixinClassName)
+    {
+        resolver.resolveMixinTypeToClassName(mixinType);
+        setReturnValue(mixinClassName);
+    }
+
+    private Mixin newMixin(String value)
+    {
+        Mixin annotation = newMock(Mixin.class);
+
+        annotation.value();
+        setReturnValue(value);
+
+        return annotation;
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/PageLoaderImplTest.java Thu Oct 26 09:30:09 2006
@@ -31,9 +31,6 @@
 import org.apache.tapestry.services.BindingSource;
 import org.testng.annotations.Test;
 
-/**
- * 
- */
 public class PageLoaderImplTest extends InternalBaseTestCase
 {
     private static final String PAGE_CLASS_NAME = "foo.page.Bar";
@@ -94,6 +91,7 @@
         ComponentPageElement rootElement = newComponentPageElement();
         InternalComponentResources resources = newInternalComponentResources();
         ComponentModel model = newComponentModel();
+        ComponentModel childModel = newComponentModel();
         ComponentTemplate template = newComponentTemplate();
         Log log = newLog();
         EmbeddedComponentModel emodel = newEmbeddedComponentModel();
@@ -148,8 +146,8 @@
         // Alas, what comes next is the recursive call to load the child element
 
         train_getComponentResources(childElement, childResources);
-        train_getComponentModel(childResources, model);
-        train_getComponentClassName(model, CHILD_CLASS_NAME);
+        train_getComponentModel(childResources, childModel);
+        train_getComponentClassName(childModel, CHILD_CLASS_NAME);
         train_getTemplate(templateSource, CHILD_CLASS_NAME, LOCALE, null);
         train_newRenderBodyElement(elementFactory, childElement, body);
         childElement.addToTemplate(body);

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java?view=diff&rev=468058&r1=468057&r2=468058
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java Thu Oct 26 09:30:09 2006
@@ -330,6 +330,74 @@
     }
 
     @Test
+    public void get_mixin_by_class_name()
+    {
+        Log log = newLog();
+        Page page = newPage();
+        ComponentLifecycle component = newComponentLifecycle();
+        ComponentModel model = newComponentModel();
+        TypeCoercer coercer = newTypeCoercer();
+        final String mixinClassName = "foo.Bar";
+        ComponentLifecycle mixin = newComponentLifecycle();
+        ComponentModel mixinModel = newComponentModel();
+
+        Instantiator ins = newInstantiator(component, model);
+        Instantiator mixinIns = newInstantiator(mixin, mixinModel);
+
+        train_getComponentClassName(mixinModel, mixinClassName);
+
+        train_getLog(model, log);
+
+        replay();
+
+        ComponentPageElement cpe = new ComponentPageElementImpl(page, ins, coercer);
+
+        cpe.addMixin(mixinIns);
+
+        assertSame(cpe.getMixinByClassName(mixinClassName), mixin);
+
+        verify();
+    }
+
+    @Test
+    public void get_mixin_by_unknown_class_name()
+    {
+        Log log = newLog();
+        Page page = newPage();
+        ComponentLifecycle component = newComponentLifecycle();
+        ComponentModel model = newComponentModel();
+        TypeCoercer coercer = newTypeCoercer();
+        ComponentLifecycle mixin = newComponentLifecycle();
+        ComponentModel mixinModel = newComponentModel();
+
+        Instantiator ins = newInstantiator(component, model);
+        Instantiator mixinIns = newInstantiator(mixin, mixinModel);
+
+        train_getComponentClassName(mixinModel, "foo.Bar");
+
+        train_getLog(model, log);
+
+        replay();
+
+        ComponentPageElement cpe = new ComponentPageElementImpl(page, ins, coercer);
+
+        cpe.addMixin(mixinIns);
+
+        try
+        {
+            cpe.getMixinByClassName("foo.Baz");
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertTrue(ex.getMessage().endsWith(" does not contain a mixin of type foo.Baz."));
+        }
+
+        verify();
+
+    }
+
+    @Test
     public void set_explicit_parameter_of_unknown_mixin()
     {
         Log log = newLog();
@@ -346,11 +414,13 @@
 
         train_getLog(model, log);
 
+        train_getComponentClassName(mixinModel, "foo.Fred");
+
         replay();
 
         ComponentPageElement cpe = new ComponentPageElementImpl(page, ins, coercer);
 
-        cpe.addMixin("Fred", mixinInstantiator);
+        cpe.addMixin(mixinInstantiator);
 
         try
         {