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 2009/02/13 19:18:53 UTC

svn commit: r744203 [2/3] - in /tapestry/tapestry5/trunk: tapestry-component-report/src/test/java/org/apache/tapestry/mojo/ tapestry-core/src/main/java/org/apache/tapestry5/annotations/ tapestry-core/src/main/java/org/apache/tapestry5/internal/ tapestr...

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java?rev=744203&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java Fri Feb 13 18:18:49 2009
@@ -0,0 +1,1015 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.pageload;
+
+import org.apache.tapestry5.Binding;
+import org.apache.tapestry5.BindingConstants;
+import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.internal.InternalComponentResources;
+import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.internal.TapestryInternalUtils;
+import org.apache.tapestry5.internal.bindings.LiteralBinding;
+import org.apache.tapestry5.internal.parser.*;
+import org.apache.tapestry5.internal.services.*;
+import org.apache.tapestry5.internal.structure.*;
+import org.apache.tapestry5.ioc.Location;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.ioc.internal.util.TapestryException;
+import org.apache.tapestry5.model.ComponentModel;
+import org.apache.tapestry5.model.EmbeddedComponentModel;
+import org.apache.tapestry5.runtime.RenderCommand;
+import org.apache.tapestry5.runtime.RenderQueue;
+import org.apache.tapestry5.services.ComponentClassResolver;
+import org.apache.tapestry5.services.InvalidationListener;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * There's still a lot of room to beef up {@link org.apache.tapestry5.internal.pageload.ComponentAssembler} and {@link
+ * org.apache.tapestry5.internal.pageload.EmbeddedComponentAssembler} to perform more static analysis.
+ */
+public class PageLoaderImpl implements PageLoader, InvalidationListener
+{
+    private static final class Key
+    {
+        private final String className;
+
+        private final Locale locale;
+
+        private Key(String className, Locale locale)
+        {
+            this.className = className;
+            this.locale = locale;
+        }
+
+        @Override
+        public boolean equals(Object o)
+        {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Key key = (Key) o;
+
+            if (!className.equals(key.className))
+                return false;
+
+            return locale.equals(key.locale);
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return 31 * className.hashCode() + locale.hashCode();
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("Key[%s, %s]", className, locale);
+        }
+    }
+
+    private static final PageAssemblyAction POP_EMBEDDED_COMPONENT_ACTION = new PageAssemblyAction()
+    {
+        public void execute(PageAssembly pageAssembly)
+        {
+            pageAssembly.flushComposableRenderCommands();
+
+            pageAssembly.createdElement.pop();
+            pageAssembly.bodyElement.pop();
+            pageAssembly.embeddedAssembler.pop();
+        }
+    };
+
+    private static final RenderCommand END_ELEMENT = new RenderCommand()
+    {
+        public void render(MarkupWriter writer, RenderQueue queue)
+        {
+            writer.end();
+        }
+
+        @Override
+        public String toString()
+        {
+            return "End";
+        }
+    };
+
+    private static final PageAssemblyAction END_ELEMENT_ACTION = new PageAssemblyAction()
+    {
+        public void execute(PageAssembly pageAssembly)
+        {
+            pageAssembly.addRenderCommand(END_ELEMENT);
+        }
+    };
+
+    private static final PageAssemblyAction FLUSH_COMPOSABLE_RENDER_COMMANDS_ACTION = new PageAssemblyAction()
+    {
+        public void execute(PageAssembly pageAssembly)
+        {
+            pageAssembly.flushComposableRenderCommands();
+        }
+    };
+
+    private final Map<Key, ComponentAssembler> cache = CollectionFactory.newConcurrentMap();
+
+    private final ComponentInstantiatorSource instantiatorSource;
+
+    private final ComponentTemplateSource templateSource;
+
+    private final PageElementFactory elementFactory;
+
+    private final ComponentPageElementResourcesSource resourcesSource;
+
+    private final ComponentClassResolver componentClassResolver;
+
+    private final PersistentFieldManager persistentFieldManager;
+
+    public PageLoaderImpl(ComponentInstantiatorSource instantiatorSource,
+                          ComponentTemplateSource templateSource, PageElementFactory elementFactory,
+                          ComponentPageElementResourcesSource resourcesSource,
+                          ComponentClassResolver componentClassResolver,
+                          PersistentFieldManager persistentFieldManager)
+    {
+        this.instantiatorSource = instantiatorSource;
+        this.templateSource = templateSource;
+        this.elementFactory = elementFactory;
+        this.resourcesSource = resourcesSource;
+        this.componentClassResolver = componentClassResolver;
+        this.persistentFieldManager = persistentFieldManager;
+    }
+
+    public void objectWasInvalidated()
+    {
+        cache.clear();
+    }
+
+    public Page loadPage(String logicalPageName, Locale locale)
+    {
+        String pageClassName = componentClassResolver.resolvePageNameToClassName(logicalPageName);
+
+        Page page = new PageImpl(logicalPageName, locale, persistentFieldManager);
+
+        ComponentAssembler assembler = getAssembler(pageClassName, locale);
+
+        ComponentPageElement rootElement = assembler.assembleRootComponent(page);
+
+        page.setRootElement(rootElement);
+
+        // 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's default state.
+
+        page.loaded();
+
+        return page;
+    }
+
+    private ComponentAssembler getAssembler(String className, Locale locale)
+    {
+        Key key = new Key(className, locale);
+
+        ComponentAssembler result = cache.get(key);
+
+        if (result == null)
+        {
+            result = createAssembler(className, locale);
+            cache.put(key, result);
+        }
+
+        return result;
+    }
+
+    private ComponentAssembler createAssembler(String className, Locale locale)
+    {
+        Instantiator instantiator = instantiatorSource.getInstantiator(className);
+
+        ComponentModel componentModel = instantiator.getModel();
+
+        ComponentTemplate template = templateSource.getTemplate(componentModel, locale);
+
+        ComponentPageElementResources resources = resourcesSource.get(locale);
+
+        ComponentAssembler assembler = new ComponentAssemblerImpl(instantiator, resources);
+
+        // "Program" the assembler by adding actions to it. The actions execute every time a new page instance
+        // is needed.
+
+        programAssembler(assembler, template);
+
+        return assembler;
+    }
+
+    /**
+     * "Programs" the assembler by analyzing the component, its mixins and its embedded components (both in the template
+     * and in the Java class), adding new PageAssemblyActions.
+     */
+    private void programAssembler(ComponentAssembler assembler, ComponentTemplate template)
+    {
+        assembler.validateEmbeddedIds(template);
+
+        // TODO: Optimize for the case of the empty template.
+
+        addImplementationMixins(assembler);
+
+        processTemplate(assembler, template);
+    }
+
+    /**
+     * Add any mixins defined for the component (implementation mixins, rather than instance mixins).
+     */
+
+    private void addImplementationMixins(ComponentAssembler assembler)
+    {
+        final List<String> mixinClassNames = assembler.getModel().getMixinClassNames();
+
+        if (mixinClassNames.isEmpty()) return;
+
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                ComponentPageElement element = pageAssembly.activeElement.peek();
+
+                for (String className : mixinClassNames)
+                {
+                    Instantiator mixinInstantiator = instantiatorSource.getInstantiator(className);
+
+                    element.addMixin(InternalUtils.lastTerm(className), mixinInstantiator);
+
+                }
+            }
+        });
+    }
+
+    private void processTemplate(ComponentAssembler assembler, ComponentTemplate template)
+    {
+        if (template.isMissing())
+        {
+            // Pretend the template has a single <t:body> element.
+
+            body(assembler);
+
+            return;
+        }
+
+        TokenStream stream = new TokenStreamImpl(template);
+
+        while (stream.more())
+        {
+            processTemplateToken(assembler, stream);
+        }
+
+        addFlushAction(assembler);
+    }
+
+    private void processTemplateToken(ComponentAssembler assembler, TokenStream stream)
+    {
+        // These tokens can appear at the top level, or at lower levels (this method is invoked
+        // from token-processing loops inside element(), component(), etc.
+
+        switch (stream.peekType())
+        {
+            case TEXT:
+
+                text(assembler, stream.next(TextToken.class));
+                break;
+
+            case EXPANSION:
+                expansion(assembler, stream.next(ExpansionToken.class));
+                break;
+
+            case BODY:
+                stream.next();
+
+                body(assembler);
+                break;
+
+            case START_ELEMENT:
+                // Will consume past matching end token
+                element(assembler, stream);
+                break;
+
+            case START_COMPONENT:
+                // Will consume past matching end token
+                component(assembler, stream);
+                break;
+
+            // ATTRIBUTE and END_ELEMENT can't happen at the top level, they're
+            // handled at a lower level. (inside element(), component(), etc.)
+
+            case COMMENT:
+                comment(assembler, stream.next(CommentToken.class));
+                break;
+
+            case BLOCK:
+                // Will consume past matching end token
+                block(assembler, stream);
+                break;
+
+            case PARAMETER:
+                // Will consume past the matching end token
+                parameter(assembler, stream);
+                break;
+
+            case DTD:
+                dtd(assembler, stream.next(DTDToken.class));
+                break;
+
+            case DEFINE_NAMESPACE_PREFIX:
+
+                defineNamespacePrefix(assembler, stream.next(DefineNamespacePrefixToken.class));
+                break;
+
+            case CDATA:
+                cdata(assembler, stream.next(CDATAToken.class));
+                break;
+
+            default:
+                throw new IllegalStateException("Not yet implemented: " + stream.peekType());
+        }
+    }
+
+    private void cdata(ComponentAssembler assembler, final CDATAToken token)
+    {
+        RenderCommand command = new RenderCommand()
+        {
+            public void render(MarkupWriter writer, RenderQueue queue)
+            {
+                writer.cdata(token.getContent());
+            }
+
+            @Override
+            public String toString()
+            {
+                return String.format("CDATA[%s]", token.getLocation());
+            }
+        };
+
+        addComposableCommand(assembler, command);
+
+    }
+
+    private void defineNamespacePrefix(ComponentAssembler assembler, final DefineNamespacePrefixToken token)
+    {
+        RenderCommand command = new RenderCommand()
+        {
+            public void render(MarkupWriter writer, RenderQueue queue)
+            {
+                writer.defineNamespace(token.getNamespaceURI(), token.getNamespacePrefix());
+            }
+
+            @Override
+            public String toString()
+            {
+                return String.format("DefineNamespace[%s %s]", token.getNamespacePrefix(), token.getNamespaceURI());
+            }
+        };
+
+
+        addComposableCommand(assembler, command);
+    }
+
+    private void dtd(ComponentAssembler assembler, final DTDToken token)
+    {
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                if (!pageAssembly.checkAndSetFlag("dtd-page-element-added"))
+                {
+                    RenderCommand command = new DTDPageElement(token.getName(), token.getPublicId(),
+                                                               token.getSystemId());
+
+                    // It doesn't really matter where this ends up in the tree as long as its inside
+                    // a portion that always renders.
+
+                    pageAssembly.addComposableRenderCommand(command);
+                }
+            }
+        });
+    }
+
+    private void parameter(ComponentAssembler assembler, TokenStream stream)
+    {
+        final ParameterToken token = stream.next(ParameterToken.class);
+
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                String parameterName = token.getName();
+
+                pageAssembly.flushComposableRenderCommands();
+
+                ComponentPageElement element = pageAssembly.createdElement.peek();
+
+                BlockImpl block = new BlockImpl(token.getLocation(),
+                                                String.format("Parameter %s of %s",
+                                                              parameterName,
+                                                              element.getCompleteId()));
+
+                Binding binding = new LiteralBinding(token.getLocation(), "block parameter " + parameterName, block);
+
+                EmbeddedComponentAssembler embeddedAssembler = pageAssembly.embeddedAssembler.peek();
+
+                ParameterBinder binder = embeddedAssembler.createBinder(parameterName, binding);
+
+                if (binder == null)
+                {
+                    String message = String.format(
+                            "Component %s does not include a parameter '%s' (and does not support informal parameters).",
+                            element.getCompleteId(),
+                            parameterName);
+
+                    throw new TapestryException(message, token.getLocation(), null);
+                }
+
+                binder.bind(pageAssembly.activeElement.peek(), element);
+
+                pageAssembly.bodyElement.push(block);
+            }
+        });
+
+        consumeToEndElementAndPopBodyElement(assembler, stream);
+    }
+
+    private void block(ComponentAssembler assembler, TokenStream stream)
+    {
+        final BlockToken token = stream.next(BlockToken.class);
+
+        final String blockId = token.getId();
+
+        // TODO: The description string should be calculatable statically, we just need
+        // to keep yet another stack (of complete component ids).
+
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                ComponentPageElement element = pageAssembly.activeElement.peek();
+
+                String description = blockId == null
+                                     ? String.format("Anonymous within %s", element.getCompleteId())
+                                     : String.format("%s within %s", blockId, element.getCompleteId());
+
+                BlockImpl block = new BlockImpl(token.getLocation(), description);
+
+                if (blockId != null)
+                    element.addBlock(blockId, block);
+
+                // Start directing template content into the Block
+                pageAssembly.flushComposableRenderCommands();
+                pageAssembly.bodyElement.push(block);
+            }
+        });
+
+        consumeToEndElementAndPopBodyElement(assembler, stream);
+    }
+
+    private void consumeToEndElementAndPopBodyElement(ComponentAssembler assembler, TokenStream stream)
+    {
+        while (true)
+        {
+            switch (stream.peekType())
+            {
+                case END_ELEMENT:
+
+                    stream.next();
+
+                    assembler.add(new PageAssemblyAction()
+                    {
+                        public void execute(PageAssembly pageAssembly)
+                        {
+                            pageAssembly.flushComposableRenderCommands();
+                            pageAssembly.bodyElement.pop();
+                        }
+                    });
+
+                    return;
+
+                default:
+                    processTemplateToken(assembler, stream);
+            }
+        }
+    }
+
+    private void comment(ComponentAssembler assembler, CommentToken token)
+    {
+        RenderCommand commentElement = new CommentPageElement(token.getComment());
+
+        addComposableCommand(assembler, commentElement);
+    }
+
+    private void component(ComponentAssembler assembler, TokenStream stream)
+    {
+        StartComponentToken token = stream.next(StartComponentToken.class);
+
+        EmbeddedComponentAssembler embeddedAssembler = startComponent(assembler, token);
+
+        while (true)
+        {
+            switch (stream.peekType())
+            {
+                case ATTRIBUTE:
+
+                    bindAttributeAsParameter(assembler, embeddedAssembler, stream.next(AttributeToken.class));
+
+                    break;
+
+                case END_ELEMENT:
+
+                    stream.next();
+
+                    assembler.add(POP_EMBEDDED_COMPONENT_ACTION);
+
+                    return;
+
+                default:
+                    processTemplateToken(assembler, stream);
+            }
+        }
+
+    }
+
+    private void bindAttributeAsParameter(ComponentAssembler assembler, EmbeddedComponentAssembler embeddedAssembler,
+                                          AttributeToken token)
+    {
+        addParameterBindingAction(assembler, embeddedAssembler, token.getName(), token.getValue(),
+                                  BindingConstants.LITERAL);
+    }
+
+    private void element(ComponentAssembler assembler, TokenStream stream)
+    {
+        StartElementToken token = stream.next(StartElementToken.class);
+
+        RenderCommand element = new StartElementPageElement(token.getNamespaceURI(), token.getName());
+
+        addComposableCommand(assembler, element);
+
+        while (true)
+        {
+            switch (stream.peekType())
+            {
+                case ATTRIBUTE:
+                    attribute(assembler, stream.next(AttributeToken.class));
+                    break;
+
+                case END_ELEMENT:
+
+                    assembler.add(END_ELEMENT_ACTION);
+
+                    stream.next();
+
+                    // Pop out a level.
+                    return;
+
+                default:
+                    processTemplateToken(assembler, stream);
+            }
+        }
+
+    }
+
+    private EmbeddedComponentAssembler startComponent(ComponentAssembler assembler, StartComponentToken token)
+    {
+        String elementName = token.getElementName();
+
+        // Initial guess: the type from the token (but this may be null in many cases).
+        String embeddedType = token.getComponentType();
+
+        // This may be null for an anonymous component.
+        String embeddedId = token.getId();
+
+        String embeddedComponentClassName = null;
+
+        final EmbeddedComponentModel embeddedModel =
+                embeddedId == null
+                ? null
+                : assembler.getModel().getEmbeddedComponentModel(embeddedId);
+
+        if (embeddedId == null)
+            embeddedId = assembler.generateEmbeddedId(embeddedType);
+
+        if (embeddedModel != null)
+        {
+            String modelType = embeddedModel.getComponentType();
+
+            if (InternalUtils.isNonBlank(modelType) && embeddedType != null)
+            {
+                String message = String.format(
+                        "Embedded component '%s' provides a type attribute in the template ('%s') " +
+                                "as well as in the component class ('%s'). " +
+                                "You should not provide a type attribute in the template when defining an embedded component " +
+                                "within the component class.",
+                        embeddedId, embeddedType, modelType
+                );
+
+                throw new TapestryException(message, token, null);
+            }
+
+            embeddedType = modelType;
+            embeddedComponentClassName = embeddedModel.getComponentClassName();
+        }
+
+        String componentClassName = embeddedComponentClassName;
+
+        // This awkwardness is making me think that the page loader should resolve the component
+        // type before invoking this method (we would then remove the componentType parameter).
+
+        if (InternalUtils.isNonBlank(embeddedType))
+        {
+            // The type actually overrides the specified class name. The class name is defined
+            // 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.
+
+            try
+            {
+                componentClassName = componentClassResolver.resolveComponentTypeToClassName(embeddedType);
+            }
+            catch (IllegalArgumentException ex)
+            {
+                throw new TapestryException(ex.getMessage(), token, ex);
+            }
+        }
+
+
+        // OK, now we can record an action to get it instantiated.
+
+        EmbeddedComponentAssembler embeddedAssembler = new EmbeddedComponentAssemblerImpl(instantiatorSource,
+                                                                                          elementFactory,
+                                                                                          componentClassName,
+                                                                                          token.getLocation());
+
+        addActionForEmbeddedComponent(assembler, embeddedAssembler, embeddedId, elementName, componentClassName,
+                                      token.getLocation());
+
+
+        addMixinActions(assembler, embeddedAssembler, embeddedModel, token.getMixins());
+
+        bindParametersFromEmbeddedModel(assembler, embeddedAssembler, embeddedModel);
+
+
+        if (embeddedModel != null && embeddedModel.getInheritInformalParameters())
+        {
+            // Another two-step:  The first "captures" the container and embedded component. The second
+            // occurs at the end of the page setup.
+
+            assembler.add(new PageAssemblyAction()
+            {
+                public void execute(PageAssembly pageAssembly)
+                {
+                    final ComponentPageElement container = pageAssembly.activeElement.peek();
+                    final ComponentPageElement embedded = pageAssembly.createdElement.peek();
+
+                    pageAssembly.deferred.add(new PageAssemblyAction()
+                    {
+                        public void execute(PageAssembly pageAssembly)
+                        {
+                            copyInformalParameters(container, embedded);
+                        }
+                    });
+                }
+            });
+
+        }
+
+        return embeddedAssembler;
+
+    }
+
+    private void copyInformalParameters(ComponentPageElement container, ComponentPageElement embedded)
+    {
+        // TODO:  Much more, this is an area where we can make things a bit more efficient by tracking
+        // what has and hasn't been bound in the EmbeddedComponentAssembler (and identifying what is
+        // and isn't informal).
+
+        ComponentModel model = embedded.getComponentResources().getComponentModel();
+
+        Map<String, Binding> informals = container.getInformalParameterBindings();
+
+        for (String name : informals.keySet())
+        {
+            if (model.getParameterModel(name) != null) continue;
+
+            Binding binding = informals.get(name);
+
+            embedded.bindParameter(name, binding);
+        }
+    }
+
+    private void bindParametersFromEmbeddedModel(ComponentAssembler assembler,
+                                                 EmbeddedComponentAssembler embeddedAssembler,
+                                                 EmbeddedComponentModel embeddedModel)
+    {
+        if (embeddedModel == null) return;
+
+        for (String name : embeddedModel.getParameterNames())
+        {
+            String parameterValue = embeddedModel.getParameterValue(name);
+
+            addParameterBindingAction(assembler, embeddedAssembler, name, parameterValue, BindingConstants.PROP);
+        }
+    }
+
+    private void addParameterBindingAction(ComponentAssembler assembler,
+                                           EmbeddedComponentAssembler embeddedAssembler,
+                                           String parameterName,
+                                           String parameterValue,
+                                           String defaultBindingPrefix)
+    {
+        if (embeddedAssembler.isBound(parameterName)) return;
+
+        embeddedAssembler.setBound(parameterName);
+
+        if (parameterValue.startsWith(InternalConstants.INHERIT_BINDING_PREFIX))
+        {
+
+            String containerParameterName = parameterValue.substring(InternalConstants.INHERIT_BINDING_PREFIX.length());
+
+            addInhertedBindingAction(assembler, parameterName, containerParameterName);
+            return;
+        }
+
+
+        final ParameterBinder binder = embeddedAssembler.createBinder(parameterName, parameterValue,
+                                                                      defaultBindingPrefix);
+
+        // Null meaning an informal parameter and the component (and mixins) doesn't support informals.
+
+        if (binder != null)
+        {
+            assembler.add(new PageAssemblyAction()
+            {
+                public void execute(PageAssembly pageAssembly)
+                {
+                    binder.bind(pageAssembly.activeElement.peek(), pageAssembly.createdElement.peek());
+                }
+            });
+        }
+    }
+
+    /**
+     * Adds a deferred action to the PageAssembly, to handle connecting the embedded components' parameter to the
+     * container component's parameter once everything else has been built.
+     *
+     * @param assembler
+     * @param parameterName
+     * @param containerParameterName
+     */
+    private void addInhertedBindingAction(ComponentAssembler assembler,
+                                          final String parameterName,
+                                          final String containerParameterName)
+    {
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                // At the time this action executes, we'll be able to capture the containing and embedded
+                // component. We can then defer the connection logic until after all other construction.
+
+                final ComponentPageElement container = pageAssembly.activeElement.peek();
+                final ComponentPageElement embedded = pageAssembly.createdElement.peek();
+
+                pageAssembly.deferred.add(new PageAssemblyAction()
+                {
+                    public void execute(PageAssembly pageAssembly)
+                    {
+                        connectInheritedParameter(container, embedded, parameterName,
+                                                  containerParameterName);
+                    }
+                });
+            }
+        });
+    }
+
+    private void connectInheritedParameter(ComponentPageElement container, ComponentPageElement embedded,
+                                           String parameterName,
+                                           String containerParameterName)
+    {
+        // TODO: This assumes that the two parameters are both on the core component and not on
+        // a mixin.
+
+        Binding containerBinding = container.getBinding(containerParameterName);
+
+        if (containerBinding == null) return;
+
+        String description = String.format("InheritedBinding[parameter %s %s(inherited from %s of %s)]",
+                                           parameterName,
+                                           embedded.getCompleteId(),
+                                           containerParameterName,
+                                           container.getCompleteId());
+
+        // This helps with debugging, and re-orients any thrown exceptions
+        // to the location of the inherited binding, rather than the container component's
+        // binding.
+
+        Binding inherited = new InheritedBinding(description, containerBinding, embedded.getLocation());
+
+        embedded.bindParameter(parameterName, inherited);
+    }
+
+    private ComponentModel getModel(String componentClassName)
+    {
+        return instantiatorSource.getInstantiator(componentClassName).getModel();
+    }
+
+    private void addMixinActions(ComponentAssembler assembler,
+                                 EmbeddedComponentAssembler embeddedAssembler, EmbeddedComponentModel model,
+                                 String mixins
+    )
+    {
+        if (model != null)
+        {
+            addMixinActionsFromEmbeddedModel(assembler, embeddedAssembler, model);
+        }
+
+        List<String> classNames = CollectionFactory.newList();
+
+        for (String typeName : TapestryInternalUtils.splitAtCommas(mixins))
+        {
+            classNames.add(componentClassResolver.resolveMixinTypeToClassName(typeName));
+        }
+
+        addMixinActionsForClassNames(assembler, embeddedAssembler, classNames);
+    }
+
+    private void addMixinActionsFromEmbeddedModel(ComponentAssembler assembler,
+                                                  EmbeddedComponentAssembler embeddedAssembler,
+                                                  EmbeddedComponentModel model
+    )
+    {
+        addMixinActionsForClassNames(assembler, embeddedAssembler, model.getMixinClassNames());
+    }
+
+    private void addMixinActionsForClassNames(ComponentAssembler assembler,
+                                              EmbeddedComponentAssembler embeddedAssembler,
+                                              final List<String> classNames
+    )
+    {
+        for (String className : classNames)
+        {
+            embeddedAssembler.addInstanceMixin(getModel(className));
+        }
+
+        if (!classNames.isEmpty())
+        {
+            assembler.add(new PageAssemblyAction()
+            {
+                public void execute(PageAssembly pageAssembly)
+                {
+                    ComponentPageElement embedded = pageAssembly.createdElement.peek();
+
+                    for (String className : classNames)
+                    {
+                        Instantiator instantiator = instantiatorSource.getInstantiator(className);
+
+                        embedded.addMixin(InternalUtils.lastTerm(className), instantiator);
+                    }
+                }
+            });
+        }
+    }
+
+    private void addActionForEmbeddedComponent(ComponentAssembler assembler,
+                                               final EmbeddedComponentAssembler embeddedAssembler,
+                                               final String embeddedId,
+                                               final String elementName,
+                                               final String componentClassName,
+                                               final Location location)
+    {
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                pageAssembly.flushComposableRenderCommands();
+
+                Locale locale = pageAssembly.page.getLocale();
+
+                ComponentAssembler assemblerForSubcomponent = getAssembler(componentClassName, locale);
+
+                // Remeber: this pushes onto to the createdElement stack, but does not pop it.
+
+                assemblerForSubcomponent.assembleEmbeddedComponent(pageAssembly, embeddedId, elementName, location);
+
+                // ... which is why we can find it via peek() here.  And it's our responsibility
+                // to clean it up.
+
+                ComponentPageElement embeddedElement = pageAssembly.createdElement.peek();
+
+                // Add the new element to the template of its container.
+
+                pageAssembly.addRenderCommand(embeddedElement);
+
+                // And redirect any futher content from this component's template to go into
+                // the body of the embedded element.
+
+                pageAssembly.bodyElement.push(embeddedElement);
+                pageAssembly.embeddedAssembler.push(embeddedAssembler);
+
+                // The means we need to pop the createdElement, bodyElement and embeddedAssembler stacks
+                // when done with this sub-component, which is what POP_EMBEDDED_COMPONENT_ACTION does.
+            }
+        });
+    }
+
+
+    private void attribute(ComponentAssembler assembler, final AttributeToken token)
+    {
+        String value = token.getValue();
+
+        // No expansion makes this easier, more efficient.
+        if (value.indexOf(InternalConstants.EXPANSION_START) < 0)
+        {
+            RenderCommand command = new RenderAttribute(token);
+
+            addComposableCommand(assembler, command);
+
+            return;
+        }
+
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                InternalComponentResources resources = pageAssembly.activeElement.peek().getComponentResources();
+
+                RenderCommand command = elementFactory.newAttributeElement(resources, token);
+
+                // Still composable, BTW.
+
+                pageAssembly.addRenderCommand(command);
+            }
+        });
+    }
+
+    private void body(ComponentAssembler assembler)
+    {
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                ComponentPageElement element = pageAssembly.activeElement.peek();
+
+                pageAssembly.addRenderCommand(new RenderBodyElement(element));
+            }
+        });
+    }
+
+    private void expansion(ComponentAssembler assembler, final ExpansionToken token)
+    {
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                ComponentResources resources = pageAssembly.activeElement.peek().getComponentResources();
+
+                // TODO: Add composably
+
+                RenderCommand command = elementFactory.newExpansionElement(resources, token);
+
+                pageAssembly.addRenderCommand(command);
+            }
+        });
+    }
+
+    private void text(ComponentAssembler assembler, TextToken textToken)
+    {
+        addComposableCommand(assembler, new TextPageElement(textToken.getText()));
+    }
+
+    private void addComposableCommand(ComponentAssembler assembler, final RenderCommand command)
+    {
+        assembler.add(new PageAssemblyAction()
+        {
+            public void execute(PageAssembly pageAssembly)
+            {
+                pageAssembly.addComposableRenderCommand(command);
+            }
+        });
+    }
+
+    private void addFlushAction(ComponentAssembler assembler)
+    {
+        assembler.add(FLUSH_COMPOSABLE_RENDER_COMMANDS_ACTION);
+    }
+
+}
+

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ParameterBinder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ParameterBinder.java?rev=744203&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ParameterBinder.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ParameterBinder.java Fri Feb 13 18:18:49 2009
@@ -0,0 +1,25 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.pageload;
+
+import org.apache.tapestry5.internal.structure.ComponentPageElement;
+
+/**
+ * Binds a parameter.
+ */
+interface ParameterBinder
+{
+    void bind(ComponentPageElement container, ComponentPageElement embedded);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/RenderAttribute.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/RenderAttribute.java?rev=744203&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/RenderAttribute.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/RenderAttribute.java Fri Feb 13 18:18:49 2009
@@ -0,0 +1,42 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.pageload;
+
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.internal.parser.AttributeToken;
+import org.apache.tapestry5.runtime.RenderCommand;
+import org.apache.tapestry5.runtime.RenderQueue;
+
+class RenderAttribute implements RenderCommand
+{
+    private final AttributeToken token;
+
+    public RenderAttribute(AttributeToken token)
+    {
+        this.token = token;
+    }
+
+    public void render(MarkupWriter writer, RenderQueue queue)
+    {
+        writer.attributeNS(token.getNamespaceURI(), token.getName(), token.getValue());
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("AttributeNS[%s %s \"%s\"]",
+                             token.getNamespaceURI(), token.getName(), token.getValue());
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/RenderBodyElement.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/RenderBodyElement.java?rev=744203&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/RenderBodyElement.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/RenderBodyElement.java Fri Feb 13 18:18:49 2009
@@ -0,0 +1,41 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.pageload;
+
+import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.internal.structure.ComponentPageElement;
+import org.apache.tapestry5.runtime.RenderCommand;
+import org.apache.tapestry5.runtime.RenderQueue;
+
+class RenderBodyElement implements RenderCommand
+{
+    private final ComponentPageElement component;
+
+    public RenderBodyElement(ComponentPageElement component)
+    {
+        this.component = component;
+    }
+
+    public void render(MarkupWriter writer, RenderQueue queue)
+    {
+        component.enqueueBeforeRenderBody(queue);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("RenderBody[%s]", component.getNestedId());
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStream.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStream.java?rev=744203&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStream.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStream.java Fri Feb 13 18:18:49 2009
@@ -0,0 +1,44 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.pageload;
+
+import org.apache.tapestry5.internal.parser.TemplateToken;
+import org.apache.tapestry5.internal.parser.TokenType;
+
+/**
+ * Provides cursor index into a {@link org.apache.tapestry5.internal.parser.ComponentTemplate}.
+ */
+interface TokenStream
+{
+    /**
+     * Returns true if there are more tokens available.
+     */
+    boolean more();
+
+    /**
+     * Peeks at the type of the next token.
+     */
+    TokenType peekType();
+
+    /**
+     * Returns the next token and casts it to the indicated type.
+     */
+    <T extends TemplateToken> T next(Class<T> type);
+
+    /**
+     * Returns the next token.
+     */
+    TemplateToken next();
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStreamImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStreamImpl.java?rev=744203&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStreamImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStreamImpl.java Fri Feb 13 18:18:49 2009
@@ -0,0 +1,63 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.pageload;
+
+import org.apache.tapestry5.internal.parser.ComponentTemplate;
+import org.apache.tapestry5.internal.parser.TemplateToken;
+import org.apache.tapestry5.internal.parser.TokenType;
+
+import java.util.List;
+
+class TokenStreamImpl implements TokenStream
+{
+    private final List<TemplateToken> tokens;
+
+    private int index = 0;
+
+    public TokenStreamImpl(ComponentTemplate template)
+    {
+        tokens = template.getTokens();
+    }
+
+    public boolean more()
+    {
+        return index < tokens.size();
+    }
+
+    public TokenType peekType()
+    {
+        checkMore();
+
+        return tokens.get(index).getTokenType();
+    }
+
+    private void checkMore()
+    {
+        if (!more())
+            throw new IllegalStateException("No more template tokens.");
+    }
+
+    public <T extends TemplateToken> T next(Class<T> type)
+    {
+        return type.cast(next());
+    }
+
+    public TemplateToken next()
+    {
+        checkMore();
+
+        return tokens.get(index++);
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSource.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSource.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSource.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSource.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@
      * @param classname FQCN to find (and perhaps transform and load)
      * @return an object which can instantiate an instance of the component
      */
-    Instantiator findInstantiator(String classname);
+    Instantiator getInstantiator(String classname);
 
     /**
      * Adds a controlled package. Only classes within controlled packages are subject to transformation.
@@ -51,7 +51,7 @@
     void addPackage(String packageName);
 
     /**
-     * Checks to see if a fully qualfied class name exists.
+     * Checks to see if a fully qualfied class name exists. This method appears to exist only for testing.
      *
      * @param className name of class to check
      * @return true if the class exists (there's a ".class" file), false otherwise

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java Fri Feb 13 18:18:49 2009
@@ -101,7 +101,8 @@
     }
 
     public ComponentInstantiatorSourceImpl(Logger logger, ClassLoader parent, ComponentClassTransformer transformer,
-                                           InternalRequestGlobals internalRequestGlobals, ClasspathURLConverter classpathURLConverter)
+                                           InternalRequestGlobals internalRequestGlobals,
+                                           ClasspathURLConverter classpathURLConverter)
     {
         this.parent = parent;
         this.transformer = transformer;
@@ -262,7 +263,7 @@
     {
     }
 
-    public synchronized Instantiator findInstantiator(String className)
+    public synchronized Instantiator getInstantiator(String className)
     {
         Instantiator result = classNameToInstantiator.get(className);
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
 import org.apache.tapestry5.services.InvalidationEventHub;
 import org.apache.tapestry5.services.UpdateListener;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -60,7 +61,7 @@
     {
         public Map<String, Location> getComponentIds()
         {
-            return null;
+            return Collections.emptyMap();
         }
 
         public Resource getResource()
@@ -70,7 +71,7 @@
 
         public List<TemplateToken> getTokens()
         {
-            return null;
+            return Collections.emptyList();
         }
 
         public boolean isMissing()
@@ -79,7 +80,8 @@
         }
     };
 
-    public ComponentTemplateSourceImpl(TemplateParser parser, PageTemplateLocator locator, ClasspathURLConverter classpathURLConverter)
+    public ComponentTemplateSourceImpl(TemplateParser parser, PageTemplateLocator locator,
+                                       ClasspathURLConverter classpathURLConverter)
     {
         this(parser, locator, new URLChangeTracker(classpathURLConverter));
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java Fri Feb 13 18:18:49 2009
@@ -15,6 +15,7 @@
 package org.apache.tapestry5.internal.services;
 
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.internal.pageload.PageLoaderImpl;
 import org.apache.tapestry5.internal.structure.ComponentPageElementResourcesSource;
 import org.apache.tapestry5.internal.structure.ComponentPageElementResourcesSourceImpl;
 import org.apache.tapestry5.ioc.ObjectLocator;
@@ -22,11 +23,7 @@
 import org.apache.tapestry5.ioc.ServiceBinder;
 import org.apache.tapestry5.ioc.annotations.*;
 import org.apache.tapestry5.ioc.internal.services.CtClassSource;
-import org.apache.tapestry5.ioc.services.Builtin;
-import org.apache.tapestry5.ioc.services.ClassFactory;
-import org.apache.tapestry5.ioc.services.ClasspathURLConverter;
-import org.apache.tapestry5.ioc.services.PerthreadManager;
-import org.apache.tapestry5.ioc.services.PropertyShadowBuilder;
+import org.apache.tapestry5.ioc.services.*;
 import org.apache.tapestry5.services.*;
 import org.slf4j.Logger;
 
@@ -43,8 +40,7 @@
 
     private final RequestGlobals requestGlobals;
 
-    public InternalModule(UpdateListenerHub updateListenerHub, ComponentInstantiatorSource componentInstantiatorSource,
-                          RequestGlobals requestGlobals)
+    public InternalModule(UpdateListenerHub updateListenerHub, RequestGlobals requestGlobals)
     {
         this.updateListenerHub = updateListenerHub;
         this.requestGlobals = requestGlobals;
@@ -74,7 +70,6 @@
         binder.bind(InternalRequestGlobals.class, InternalRequestGlobalsImpl.class);
         binder.bind(EndOfRequestEventHub.class);
         binder.bind(PageActivationContextCollector.class);
-        binder.bind(PageLoader.class, PageLoaderImpl.class);
         binder.bind(ResponseCompressionAnalyzer.class, ResponseCompressionAnalyzerImpl.class);
         binder.bind(LinkFactory.class, LinkFactoryImpl.class);
     }
@@ -119,7 +114,7 @@
             @Inject
             @Symbol(SymbolConstants.APPLICATION_CATALOG)
             String appCatalog,
-            
+
             ClasspathURLConverter classpathURLConverter)
     {
         ComponentMessagesSourceImpl service = new ComponentMessagesSourceImpl(contextAssetFactory
@@ -137,7 +132,7 @@
                                                                         Logger logger,
 
                                                                         InternalRequestGlobals internalRequestGlobals,
-                                                                        
+
                                                                         ClasspathURLConverter classpathURLConverter)
     {
         ComponentInstantiatorSourceImpl source = new ComponentInstantiatorSourceImpl(logger, classFactory
@@ -156,6 +151,27 @@
         return transformer;
     }
 
+    public PageLoader buildPageLoader(@Autobuild PageLoaderImpl service,
+
+                                      @ComponentClasses
+                                      InvalidationEventHub classesHub,
+
+                                      @ComponentTemplates
+                                      InvalidationEventHub templatesHub,
+
+                                      @ComponentMessages
+                                      InvalidationEventHub messagesHub)
+    {
+        // TODO: We could combine these three using chain-of-command.
+
+        classesHub.addInvalidationListener(service);
+        templatesHub.addInvalidationListener(service);
+        messagesHub.addInvalidationListener(service);
+
+        return service;
+    }
+
+
     public PagePool buildPagePool(@Autobuild PagePoolImpl service,
 
                                   @ComponentClasses
@@ -244,4 +260,5 @@
     {
         return builder.build(componentInstantiatorSource, "classSource", CtClassSource.class);
     }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageElementFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageElementFactory.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageElementFactory.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageElementFactory.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -25,8 +25,6 @@
 import org.apache.tapestry5.runtime.RenderCommand;
 import org.apache.tapestry5.services.BindingSource;
 
-import java.util.Locale;
-
 /**
  * Used by the {@link org.apache.tapestry5.internal.services.PageLoader} to create page elements
  */
@@ -69,10 +67,9 @@
      *
      * @param page      the page that will contain the root component
      * @param className the fully qualified class name of the root component
-     * @param locale    the locale for the page
      * @return the root page element
      */
-    ComponentPageElement newRootComponentElement(Page page, String className, Locale locale);
+    ComponentPageElement newRootComponentElement(Page page, String className);
 
     /**
      * Adds a mixin to the element, resolving the mixin type to a mixin class.

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageElementFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageElementFactoryImpl.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageElementFactoryImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageElementFactoryImpl.java Fri Feb 13 18:18:49 2009
@@ -18,6 +18,7 @@
 import org.apache.tapestry5.BindingConstants;
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.MarkupWriter;
+import org.apache.tapestry5.internal.InternalConstants;
 import org.apache.tapestry5.internal.parser.AttributeToken;
 import org.apache.tapestry5.internal.parser.ExpansionToken;
 import org.apache.tapestry5.internal.structure.*;
@@ -33,7 +34,6 @@
 import org.apache.tapestry5.services.ComponentClassResolver;
 
 import java.util.List;
-import java.util.Locale;
 
 public class PageElementFactoryImpl implements PageElementFactory
 {
@@ -47,8 +47,6 @@
 
     private final ComponentPageElementResourcesSource componentPageElementResourcesSource;
 
-    private static final String EXPANSION_START = "${";
-
     private static class LiteralStringProvider implements StringProvider
     {
         private final String string;
@@ -106,7 +104,7 @@
 
         while (true)
         {
-            int expansionx = expression.indexOf(EXPANSION_START, startx);
+            int expansionx = expression.indexOf(InternalConstants.EXPANSION_START, startx);
 
             // No more expansions, add in the rest of the string as a literal.
 
@@ -204,8 +202,7 @@
 
                 try
                 {
-                    finalClassName = componentClassResolver
-                            .resolveComponentTypeToClassName(componentType);
+                    finalClassName = componentClassResolver.resolveComponentTypeToClassName(componentType);
                 }
                 catch (IllegalArgumentException ex)
                 {
@@ -213,8 +210,7 @@
                 }
             }
 
-            Instantiator instantiator = componentInstantiatorSource
-                    .findInstantiator(finalClassName);
+            Instantiator instantiator = componentInstantiatorSource.getInstantiator(finalClassName);
 
             // This is actually a good place to check for recursive templates, here where we've
             // resolved
@@ -257,11 +253,12 @@
         }
     }
 
-    public ComponentPageElement newRootComponentElement(Page page, String componentType, Locale locale)
+    public ComponentPageElement newRootComponentElement(Page page, String componentType)
     {
-        Instantiator instantiator = componentInstantiatorSource.findInstantiator(componentType);
+        Instantiator instantiator = componentInstantiatorSource.getInstantiator(componentType);
 
-        ComponentPageElementResources componentPageElementResources = componentPageElementResourcesSource.get(locale);
+        ComponentPageElementResources componentPageElementResources = componentPageElementResourcesSource.get(
+                page.getLocale());
 
         ComponentPageElement result = new ComponentPageElementImpl(page, instantiator, componentPageElementResources);
 
@@ -288,9 +285,11 @@
 
     public void addMixinByClassName(ComponentPageElement component, String mixinClassName)
     {
-        Instantiator mixinInstantiator = componentInstantiatorSource.findInstantiator(mixinClassName);
+        Instantiator mixinInstantiator = componentInstantiatorSource.getInstantiator(mixinClassName);
+
+        String mixinId = InternalUtils.lastTerm(mixinClassName);
 
-        component.addMixin(mixinInstantiator);
+        component.addMixin(mixinId, mixinInstantiator);
     }
 
     public Binding newBinding(String parameterName, ComponentResources loadingComponentResources,
@@ -298,7 +297,7 @@
                               String expression, Location location)
     {
 
-        if (expression.contains(EXPANSION_START))
+        if (expression.contains(InternalConstants.EXPANSION_START))
         {
             StringProvider provider = parseAttributeExpansionExpression(expression, loadingComponentResources,
                                                                         location);

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry5.internal.structure;
 
+import org.apache.tapestry5.Binding;
 import org.apache.tapestry5.Block;
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.ComponentResourcesCommon;
@@ -70,9 +71,18 @@
     /**
      * Adds a mixin.
      *
+     * @param mixinId      a unique id for the mixin, the last term of the mixin's class name
      * @param instantiator used to instantiate an instance of the mixin
      */
-    void addMixin(Instantiator instantiator);
+    void addMixin(String mixinId, Instantiator instantiator);
+
+    /**
+     * @param mixinId       id of previously added mixin
+     * @param parameterName simple (unqualified) name of parameter
+     * @param binding       binding for parameter
+     * @since 5.1.0.0
+     */
+    void bindMixinParameter(String mixinId, String parameterName, Binding binding);
 
     /**
      * Retrieves a component page element by its id. The search is caseless.

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java Fri Feb 13 18:18:49 2009
@@ -18,7 +18,6 @@
 import org.apache.tapestry5.annotations.*;
 import org.apache.tapestry5.dom.Element;
 import org.apache.tapestry5.internal.InternalComponentResources;
-import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.internal.services.ComponentEventImpl;
 import org.apache.tapestry5.internal.services.EventImpl;
 import org.apache.tapestry5.internal.services.Instantiator;
@@ -663,12 +662,14 @@
 
         if (existing != null)
             throw new TapestryException(StructureMessages.duplicateChildComponent(this, childId), child,
-                    new TapestryException(StructureMessages.originalChildComponent(this, childId, existing.getLocation()), existing, null));
+                                        new TapestryException(StructureMessages.originalChildComponent(this, childId,
+                                                                                                       existing.getLocation()),
+                                                              existing, null));
 
         children.put(childId, child);
     }
 
-    public void addMixin(Instantiator instantiator)
+    public void addMixin(String mixinId, Instantiator instantiator)
     {
         if (mixinIdToComponentResources == null)
         {
@@ -676,10 +677,8 @@
             components = CollectionFactory.newList();
         }
 
-        String mixinClassName = instantiator.getModel().getComponentClassName();
-        String mixinName = TapestryInternalUtils.lastTerm(mixinClassName);
 
-        String mixinExtension = "$" + mixinName.toLowerCase();
+        String mixinExtension = "$" + mixinId.toLowerCase();
 
         InternalComponentResourcesImpl resources = new InternalComponentResourcesImpl(page, this, coreResources,
                                                                                       elementResources,
@@ -687,13 +686,31 @@
                                                                                       nestedId + mixinExtension,
                                                                                       instantiator);
 
-        // TODO: Check for name collision?
+        if (mixinIdToComponentResources.containsKey(mixinId))
+            throw new RuntimeException(
+                    String.format("Component %s already has a '%s' mixin.", getCompleteId(), mixinId));
 
-        mixinIdToComponentResources.put(mixinName, resources);
+        mixinIdToComponentResources.put(mixinId, resources);
 
         components.add(resources.getComponent());
     }
 
+    public void bindMixinParameter(String mixinId, String parameterName, Binding binding)
+    {
+        InternalComponentResources mixinResources = InternalUtils.get(mixinIdToComponentResources, mixinId);
+
+        if (mixinResources == null)
+            throw new TapestryException(
+                    StructureMessages.missingMixinForParameter(completeId, mixinId, parameterName), binding, null);
+
+        mixinResources.bindParameter(parameterName, binding);
+    }
+
+    public Binding getBinding(String parameterName)
+    {
+        return coreResources.getBinding(parameterName);
+    }
+
     public void bindParameter(String parameterName, Binding binding)
     {
         // Maybe should use colon here? Depends on what works best in the template,
@@ -702,15 +719,11 @@
 
         if (dotx > 0)
         {
-            String mixinName = parameterName.substring(0, dotx);
-            InternalComponentResources mixinResources = InternalUtils.get(mixinIdToComponentResources, mixinName);
-
-            if (mixinResources == null) throw new TapestryException(
-                    StructureMessages.missingMixinForParameter(completeId, mixinName, parameterName), binding, null);
-
+            String mixinId = parameterName.substring(0, dotx);
             String simpleName = parameterName.substring(dotx + 1);
 
-            mixinResources.bindParameter(simpleName, binding);
+            bindMixinParameter(mixinId, simpleName, binding);
+
             return;
         }
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java Fri Feb 13 18:18:49 2009
@@ -304,7 +304,7 @@
         return getParameterAccess(parameterName).getBoundType();
     }
 
-    private Binding getBinding(String parameterName)
+    public Binding getBinding(String parameterName)
     {
         return InternalUtils.get(bindings, parameterName);
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ComponentWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ComponentWorker.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ComponentWorker.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ComponentWorker.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 import org.apache.tapestry5.internal.TapestryInternalUtils;
 import org.apache.tapestry5.ioc.Location;
 import org.apache.tapestry5.ioc.internal.services.StringLocation;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.model.MutableComponentModel;
@@ -66,6 +67,14 @@
 
             addParameters(embedded, annotation.parameters());
 
+
+            String names = annotation.publishParameters();
+            if (InternalUtils.isNonBlank(names))
+            {
+                embedded.setPublishedParameters(CollectionFactory.newList(TapestryInternalUtils.splitAtCommas(names)));
+            }
+
+
             transformation.makeReadOnly(fieldName);
 
             String body = String.format("%s = (%s) %s.getEmbeddedComponent(\"%s\");", fieldName, type,

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/EmbeddedComponentModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/EmbeddedComponentModel.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/EmbeddedComponentModel.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/EmbeddedComponentModel.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008 The Apache Software Foundation
+// Copyright 2006, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -60,4 +60,13 @@
      * If true, then the component should inherit informal parameters from its container.
      */
     boolean getInheritInformalParameters();
+
+    /**
+     * Returns the list of published parameters of this component (usually an empty list).
+     *
+     * @return list of parameter names to publish
+     * @see org.apache.tapestry5.annotations.Component#publishParameters()
+     * @since 5.1.0.0
+     */
+    List<String> getPublishedParameters();
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/MutableEmbeddedComponentModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/MutableEmbeddedComponentModel.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/MutableEmbeddedComponentModel.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/MutableEmbeddedComponentModel.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2006 The Apache Software Foundation
+// Copyright 2006, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,6 +14,8 @@
 
 package org.apache.tapestry5.model;
 
+import java.util.List;
+
 /**
  * A mutable version of {@link org.apache.tapestry5.model.EmbeddedComponentModel} that allows the parameters to be
  * incrementally stored.
@@ -26,4 +28,13 @@
      * Adds a mixin to the component in terms of its fully qualified class name.
      */
     void addMixin(String mixinClassName);
+
+    /**
+     * Sets the list of published parameters for this embedded component.
+     *
+     * @param parameterNames list of names
+     * @see org.apache.tapestry5.annotations.Component#publishParameters()
+     * @since 5.1.0.0
+     */
+    void setPublishedParameters(List<String> parameterNames);
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/MarkupUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/MarkupUtilsTest.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/MarkupUtilsTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/MarkupUtilsTest.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2007, 2008 The Apache Software Foundation
+// Copyright 2007, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@
         assertEquals(MarkupUtils.quote(input), expected);
     }
 
-    @DataProvider(name = "string_quoting_input")
+    @DataProvider
     public Object[][] string_quoting_input()
     {
         return new Object[][]
@@ -55,7 +55,7 @@
         assertEquals(MarkupUtils.join(list), expected);
     }
 
-    @DataProvider(name = "join_input")
+    @DataProvider
     public Object[][] join_input()
     {
         return new Object[][]

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/pagelevel/DTDTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/pagelevel/DTDTest.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/pagelevel/DTDTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/pagelevel/DTDTest.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
 
     private static final String STRICT = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">";
 
-    @DataProvider(name = "dtd_page_provider")
+    @DataProvider
     public Object[][] dtd_page_provider()
     {
         return new Object[][]

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/TapestryInternalUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/TapestryInternalUtilsTest.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/TapestryInternalUtilsTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/TapestryInternalUtilsTest.java Fri Feb 13 18:18:49 2009
@@ -57,13 +57,13 @@
     }
 
 
-    @Test(dataProvider = "to_user_presentable")
+    @Test(dataProvider = "to_user_presentable_data")
     public void to_user_presentable(String input, String expected)
     {
         assertEquals(TapestryInternalUtils.toUserPresentable(input), expected);
     }
 
-    @DataProvider(name = "to_user_presentable")
+    @DataProvider
     public Object[][] to_user_presentable_data()
     {
         return new Object[][] { { "hello", "Hello" }, { "userId", "User Id" }, { "useHTML", "Use HTML" },
@@ -372,14 +372,27 @@
         assertEquals(TapestryInternalUtils.toClassAttributeValue(classes), "fred barney wilma");
     }
 
-    @Test
-    public void split_at_commas()
+    @DataProvider
+    public Object[][] split_at_commas_data()
+    {
+        return new Object[][]
+                {
+                        { "foo", new String[] { "foo" } },
+                        { "foo, bar", new String[] { "foo", "bar" } },
+                        { "  foo, \nbar\t\t", new String[] { "foo", "bar" } },
+                        { null, new String[0] }
+                };
+    }
+
+
+    @Test(dataProvider = "split_at_commas_data")
+    public void split_at_commas(String input, String[] output)
     {
-        assertArraysEqual(TapestryInternalUtils.splitAtCommas("foo"), "foo");
-        assertArraysEqual(TapestryInternalUtils.splitAtCommas("foo , bar"), "foo", "bar");
+        assertArraysEqual(TapestryInternalUtils.splitAtCommas(input), output);
+
     }
 
-    @DataProvider(name = "to_base64_data")
+    @DataProvider
     public Object[][] to_base64_data()
     {
         return new Object[][] {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/beaneditor/BeanModelUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/beaneditor/BeanModelUtilsTest.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/beaneditor/BeanModelUtilsTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/beaneditor/BeanModelUtilsTest.java Fri Feb 13 18:18:49 2009
@@ -17,9 +17,9 @@
 import org.apache.tapestry5.beaneditor.BeanModel;
 import org.apache.tapestry5.beaneditor.PropertyModel;
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
+import org.easymock.EasyMock;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
-import org.easymock.EasyMock;
 
 public class BeanModelUtilsTest extends InternalBaseTestCase
 {
@@ -35,7 +35,7 @@
                 { propertyNames, expected };
     }
 
-    @DataProvider(name = "split_inputs")
+    @DataProvider
     public Object[][] split_inputs()
     {
         return new Object[][]

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/bindings/PropBindingFactoryTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/bindings/PropBindingFactoryTest.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/bindings/PropBindingFactoryTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/bindings/PropBindingFactoryTest.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -573,33 +573,33 @@
         verify();
     }
 
-    @DataProvider(name = "values")
+    @DataProvider
     public Object[][] values()
     {
         return new Object[][]
                 {
-                        {"true", true,},
-                        {"True", true,},
-                        {" true ", true,},
-                        {"false", false},
-                        {"null", null},
-                        {"3", 3l},
-                        {" 37 ", 37l},
-                        {" -227", -227l},
-                        {" 5.", 5d},
-                        {" -100.", -100d},
-                        {" -0.0 ", -0d},
-                        {"+50", 50l},
-                        {"+7..+20", new IntegerRange(7, 20)},
-                        {"+5.5", 5.5d},
-                        {"1..10", new IntegerRange(1, 10)},
-                        {" -20 .. -30 ", new IntegerRange(-20, -30)},
-                        {"0.", 0d},
-                        {" 227.75", 227.75d},
-                        {" -10123.67", -10123.67d},
-                        {"'Hello World'", "Hello World"},
-                        {" 'Whitespace Ignored' ", "Whitespace Ignored"},
-                        {" ' Inside ' ", " Inside "}
+                        { "true", true, },
+                        { "True", true, },
+                        { " true ", true, },
+                        { "false", false },
+                        { "null", null },
+                        { "3", 3l },
+                        { " 37 ", 37l },
+                        { " -227", -227l },
+                        { " 5.", 5d },
+                        { " -100.", -100d },
+                        { " -0.0 ", -0d },
+                        { "+50", 50l },
+                        { "+7..+20", new IntegerRange(7, 20) },
+                        { "+5.5", 5.5d },
+                        { "1..10", new IntegerRange(1, 10) },
+                        { " -20 .. -30 ", new IntegerRange(-20, -30) },
+                        { "0.", 0d },
+                        { " 227.75", 227.75d },
+                        { " -10123.67", -10123.67d },
+                        { "'Hello World'", "Hello World" },
+                        { " 'Whitespace Ignored' ", "Whitespace Ignored" },
+                        { " ' Inside ' ", " Inside " }
                 };
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/pageload/CompositeRenderCommandTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/pageload/CompositeRenderCommandTest.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/pageload/CompositeRenderCommandTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/pageload/CompositeRenderCommandTest.java Fri Feb 13 18:18:49 2009
@@ -1,4 +1,4 @@
-// Copyright 2008 The Apache Software Foundation
+// Copyright 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@
 
 public class CompositeRenderCommandTest extends InternalBaseTestCase
 {
-    @DataProvider(name = "nyi")
+    @DataProvider
     public Object[][] nyi_data()
     {
         RenderCommand push = new RenderCommand()
@@ -64,7 +64,7 @@
         };
     }
 
-    @Test(dataProvider = "nyi")
+    @Test(dataProvider = "nyi_data")
     public void render_queue_commands_nyi
             (RenderCommand
                     rc)
@@ -74,7 +74,7 @@
 
         try
         {
-            new CompositeRenderCommand(new RenderCommand[] {rc}).render(writer, queue);
+            new CompositeRenderCommand(new RenderCommand[] { rc }).render(writer, queue);
 
             unreachable();
         }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java?rev=744203&r1=744202&r2=744203&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ClasspathAssetAliasManagerImplTest.java Fri Feb 13 18:18:49 2009
@@ -55,7 +55,7 @@
         verify();
     }
 
-    @DataProvider(name = "to_client_url_data")
+    @DataProvider
     public Object[][] to_client_url_data()
     {
         return new Object[][] { { "foo/bar/Baz.txt", "classpath/1.0/foo/bar/Baz.txt" },
@@ -73,7 +73,7 @@
         assertEquals(manager.toResourcePath(clientURL), expectedResourcePath);
     }
 
-    @DataProvider(name = "to_resource_path_data")
+    @DataProvider
     public Object[][] to_resource_path_data()
     {
         Object[][] data = to_client_url_data();