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 2007/01/12 02:57:40 UTC

svn commit: r495465 [1/2] - in /tapestry/tapestry5/tapestry-core/trunk/src: main/java/org/apache/tapestry/ main/java/org/apache/tapestry/internal/parser/ main/java/org/apache/tapestry/internal/services/ main/java/org/apache/tapestry/internal/structure/...

Author: hlship
Date: Thu Jan 11 17:57:38 2007
New Revision: 495465

URL: http://svn.apache.org/viewvc?view=rev&rev=495465
Log:
Rework the PageLoader to property support multiple threads.
Begin work on adding support for <t:block> and <t:parameter> in templates.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Block.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/BlockToken.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/ParameterToken.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BlockImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BodyPageElement.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/BlockImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/block_element.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_blank.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_omitted.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/parameter_element.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/unexpected_attribute_in_block_element.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/unexpected_attribute_in_parameter_element.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/unexpected_element_in_tapestry_namespace.html
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/EndElementToken.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/TokenType.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/services/TemplateParserImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.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/services/tapestry_5_0_0.xsd
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/TemplateParserImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ParameterConflict.html

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Block.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Block.java?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Block.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/Block.java Thu Jan 11 17:57:38 2007
@@ -0,0 +1,26 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry;
+
+/**
+ * A block is a collection of static text and elements, and components, derived from a component
+ * template. In the template, a block is demarcated using the &lt;t:block&gt; or &lt;t:parameter&gt;
+ * elements. The interface defines no methods, but the provided implementations of Block are capable
+ * of rendering their contents on demand.
+ */
+public interface Block
+{
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/BlockToken.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/BlockToken.java?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/BlockToken.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/BlockToken.java Thu Jan 11 17:57:38 2007
@@ -0,0 +1,51 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.internal.parser;
+
+import org.apache.tapestry.ioc.Location;
+
+/**
+ * A block, used to enclose a chunk of template (including components) and control when or if the
+ * content is rendered.
+ */
+public class BlockToken extends TemplateToken
+{
+    private final String _id;
+
+    /**
+     * @param id
+     *            the id of the block, or null for an annonymous block
+     * @param location
+     *            of the block element
+     */
+    public BlockToken(String id, Location location)
+    {
+        super(TokenType.BLOCK, location);
+
+        _id = id;
+    }
+
+    /** Returns the block's template-unique id, or null if the block element did not specify an id. */
+    public String getId()
+    {
+        return _id;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("Block[%s]", _id == null ? "" : _id);
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/EndElementToken.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/EndElementToken.java?view=diff&rev=495465&r1=495464&r2=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/EndElementToken.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/EndElementToken.java Thu Jan 11 17:57:38 2007
@@ -17,7 +17,7 @@
 import org.apache.tapestry.ioc.Location;
 
 /**
- * Ends a previously started element (including components, parameters, etc.).
+ * Ends a previously started element (including components, parameters, blocks, etc.).
  */
 public class EndElementToken extends TemplateToken
 {

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/ParameterToken.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/ParameterToken.java?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/ParameterToken.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/ParameterToken.java Thu Jan 11 17:57:38 2007
@@ -0,0 +1,47 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.internal.parser;
+
+import org.apache.tapestry.ioc.Location;
+
+/** A parameter block to be passed to a component as a parameter. */
+public class ParameterToken extends TemplateToken
+{
+    private final String _name;
+
+    /**
+     * @param name
+     *            the name of the parameter to be bound
+     * @param location
+     *            location of the element
+     */
+    public ParameterToken(String name, Location location)
+    {
+        super(TokenType.PARAMETER, location);
+
+        _name = name;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("Parameter[%s]", _name);
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/TokenType.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/TokenType.java?view=diff&rev=495465&r1=495464&r2=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/TokenType.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/parser/TokenType.java Thu Jan 11 17:57:38 2007
@@ -12,15 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.parser;
-
-/**
- * Defines the different types of {@link org.apache.tapestry.internal.parser.TemplateToken}s. Each
- * value maps to a particular subclass of TemplateToken.
- * 
- * 
- */
-public enum TokenType {
-
-    ATTRIBUTE, CDATA, COMMENT, END_ELEMENT, START_COMPONENT, START_ELEMENT, TEXT, BODY, EXPANSION
-}
+package org.apache.tapestry.internal.parser;
+
+/**
+ * Defines the different types of {@link org.apache.tapestry.internal.parser.TemplateToken}s. Each
+ * value maps to a particular subclass of TemplateToken.
+ */
+public enum TokenType {
+
+    ATTRIBUTE, CDATA, COMMENT, END_ELEMENT, START_COMPONENT, START_ELEMENT, TEXT, BODY, EXPANSION, PARAMETER, BLOCK
+}

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=495465&r1=495464&r2=495465
==============================================================================
--- 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 Jan 11 17:57:38 2007
@@ -14,36 +14,11 @@
 
 package org.apache.tapestry.internal.services;
 
-import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
-
-import java.util.List;
 import java.util.Locale;
-import java.util.Set;
 
-import org.apache.commons.logging.Log;
-import org.apache.tapestry.Binding;
-import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.events.InvalidationListener;
-import org.apache.tapestry.internal.InternalConstants;
 import org.apache.tapestry.internal.event.InvalidationEventHubImpl;
-import org.apache.tapestry.internal.parser.AttributeToken;
-import org.apache.tapestry.internal.parser.CommentToken;
-import org.apache.tapestry.internal.parser.ComponentTemplate;
-import org.apache.tapestry.internal.parser.ExpansionToken;
-import org.apache.tapestry.internal.parser.StartComponentToken;
-import org.apache.tapestry.internal.parser.StartElementToken;
-import org.apache.tapestry.internal.parser.TemplateToken;
-import org.apache.tapestry.internal.parser.TextToken;
-import org.apache.tapestry.internal.structure.ComponentPageElement;
 import org.apache.tapestry.internal.structure.Page;
-import org.apache.tapestry.internal.structure.PageElement;
-import org.apache.tapestry.internal.structure.PageImpl;
-import org.apache.tapestry.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry.ioc.internal.util.IdAllocator;
-import org.apache.tapestry.ioc.internal.util.InternalUtils;
-import org.apache.tapestry.ioc.internal.util.TapestryException;
-import org.apache.tapestry.model.ComponentModel;
-import org.apache.tapestry.model.EmbeddedComponentModel;
 import org.apache.tapestry.services.BindingSource;
 import org.apache.tapestry.services.PersistentFieldManager;
 
@@ -60,10 +35,6 @@
 
     private final PersistentFieldManager _persistentFieldManager;
 
-    private Page _page;
-
-    private Locale _locale;
-
     public PageLoaderImpl(ComponentTemplateSource templateSource,
             PageElementFactory pageElementFactory, BindingSource bindingSource,
             LinkFactory linkFactory, PersistentFieldManager persistentFieldManager)
@@ -76,419 +47,19 @@
     }
 
     /**
-     * Stack of elements within the current template, used to determine on which object to invoke
-     * {@link ComponentPageElement#addToBody(PageElement)}. Each new element (component or
-     * otherwise) will push a value onto this stack, each end element will pop a value off the
-     * stack.
-     */
-    private final List<ComponentPageElement> _elementStack = newList();
-
-    private final List<ComponentPageElement> _componentQueue = newList();
-
-    /**
      * For the moment, this service is a singleton. However, only a single page can be built at one
-     * time. The coming rework will shift the local variables to a secondary process object and
+     * time. The coming rework will shift the loc al variables to a secondary process object and
      * allow the loader to work in parallel.
      */
-    public synchronized Page loadPage(String pageClassName, Locale locale)
-    {
-        try
-        {
-            _locale = locale;
-
-            _page = new PageImpl(pageClassName, _locale, _linkFactory, _persistentFieldManager);
-
-            loadRootComponent(pageClassName);
-
-            workComponentQueue();
-
-            // The page is *loaded* before it is attached to the request.
-            // This is to help ensure that no client-specific information leaks
-            // into the page.
-
-            _page.loaded();
-
-            return _page;
-        }
-        finally
-        {
-            _page = null;
-            _locale = null;
-            _elementStack.clear();
-            _componentQueue.clear();
-        }
-    }
-
-    private void loadRootComponent(String className)
-    {
-        ComponentPageElement rootComponent = _pageElementFactory.newRootComponentElement(
-                _page,
-                className);
-
-        _page.setRootElement(rootComponent);
-
-        push(_componentQueue, rootComponent);
-    }
-
-    /** Works the component queue, until exausted. */
-    private void workComponentQueue()
-    {
-        while (!_componentQueue.isEmpty())
-        {
-            ComponentPageElement componentElement = pop(_componentQueue);
-
-            loadTemplateForComponent(componentElement);
-        }
-    }
-
-    /**
-     * Do you smell something? I'm smelling that this class needs to be redesigned to not need a
-     * central method this large and hard to test. I think a lot of instance and local variables
-     * need to be bundled up into some kind of process object. This code is effectively too big to
-     * be tested except through integration testing.
-     */
-    private void loadTemplateForComponent(ComponentPageElement loadingElement)
+    public Page loadPage(String pageClassName, Locale locale)
     {
-        ComponentModel model = loadingElement.getComponentResources().getComponentModel();
-
-        String componentClassName = model.getComponentClassName();
-        ComponentTemplate template = _templateSource.getTemplate(model, _locale);
-
-        // TODO: This needs some work, because the component may have defined embedded components
-        // that we need to log errors about.
-
-        // When the template for a component is missing, we pretend it consists of just a RenderBody
-        // phase. Missing is not an error ... many component simply do not have a template.
-
-        if (template.isMissing())
-        {
-            addRenderBodyElement(loadingElement);
-            return;
-        }
-
-        // Pre-allocate ids to avoid later name collisions.
-
-        Log log = model.getLog();
-
-        Set<String> embeddedIds = CollectionFactory.newSet(model.getEmbeddedComponentIds());
-
-        IdAllocator idAllocator = new IdAllocator();
-        for (String id : template.getComponentIds())
-        {
-            idAllocator.allocateId(id);
-            embeddedIds.remove(id);
-        }
-
-        if (!embeddedIds.isEmpty())
-            log.error(ServicesMessages.embeddedComponentsNotInTemplate(
-                    embeddedIds,
-                    componentClassName));
-
-        // Here's the thing. Stuff in this template is part of element's template unless
-        // it is inside another component, in which case, it is part of the component's
-        // body. template is stuff from the component's own template (if any). body
-        // is stuff from its container's template.
-
-        PageElement activePageElement = loadingElement;
-        ComponentPageElement activeComponent = loadingElement;
-        List<PageElement> activePageElementStack = newList();
-        List<ComponentPageElement> activeComponentStack = newList();
-
-        boolean directlyInsideSubcomponent = false;
-
-        for (TemplateToken token : template.getTokens())
-        {
-            switch (token.getTokenType())
-            {
-                // case CDATA: -- not yet supported
-
-                case COMMENT:
-                    add(loadingElement, activeComponent, _pageElementFactory
-                            .newCommentElement((CommentToken) token));
-                    break;
-
-                case TEXT:
-
-                    add(loadingElement, activeComponent, _pageElementFactory
-                            .newTextElement((TextToken) token));
-                    break;
-
-                case EXPANSION:
-
-                    add(loadingElement, activeComponent, _pageElementFactory.newExpansionElement(
-                            loadingElement.getComponentResources(),
-                            (ExpansionToken) token));
-                    break;
-
-                case START_ELEMENT:
-
-                    PageElement newElement = _pageElementFactory
-                            .newStartElement((StartElementToken) token);
-
-                    add(loadingElement, activeComponent, newElement);
-
-                    // We push onto *both* stacks when starting an element or when
-                    // starting a component, because we can't differentitate cases
-                    // when we hit the end element token.
-
-                    push(activePageElementStack, activePageElement);
-                    push(activeComponentStack, activeComponent);
-
-                    activePageElement = newElement;
-
-                    // Any additional attribute tokens should be directed to
-                    // the activePageElement, and not be directed to
-                    // the activeComponent.
-
-                    directlyInsideSubcomponent = false;
-
-                    break;
-
-                case BODY:
-
-                    addRenderBodyElement(loadingElement);
-
-                    // BODY tokens are *not* matched by END_ELEMENT tokens. Nor will there be
-                    // text or comment content "inside" the BODY.
-
-                    break;
-
-                case END_ELEMENT:
-
-                    // Filter out the end element for an embedded component. That's not part of the
-                    // component's body or the containing component's template (just the RenderBody
-                    // page element is part of the component's template).
-
-                    if (activePageElement != activeComponent || activeComponent == loadingElement)
-                        add(loadingElement, activeComponent, _pageElementFactory.newEndElement());
-
-                    activePageElement = pop(activePageElementStack);
-                    activeComponent = pop(activeComponentStack);
-
-                    break;
-
-                case START_COMPONENT:
-
-                    // The container for any components is the loading component, regardless of
-                    // how the component elements are nested within the loading component's
-                    // template.
-
-                    StartComponentToken startComponent = (StartComponentToken) token;
-
-                    String elementName = startComponent.getElementName();
+        // For the moment, the processors are used once and discarded. Perhaps it is worth the
+        // effort to pool them for reuse, but not too likely.
 
-                    String embeddedType = startComponent.getType();
+        PageLoaderProcessor processor = new PageLoaderProcessor(_templateSource,
+                _pageElementFactory, _bindingSource, _linkFactory, _persistentFieldManager);
 
-                    String embeddedId = startComponent.getId();
-
-                    String embeddedComponentClassName = null;
-
-                    // We know that if embeddedId is null, embeddedType is not.
-
-                    if (embeddedId == null)
-                        embeddedId = generateEmbeddedId(embeddedType, idAllocator);
-
-                    EmbeddedComponentModel embeddedModel = model
-                            .getEmbeddedComponentModel(embeddedId);
-
-                    if (embeddedModel != null)
-                    {
-                        String modelType = embeddedModel.getComponentType();
-
-                        if (InternalUtils.isNonBlank(modelType) && embeddedType != null)
-                            log.error(ServicesMessages.compTypeConflict(
-                                    embeddedId,
-                                    embeddedType,
-                                    modelType));
-
-                        embeddedType = modelType;
-                        embeddedComponentClassName = embeddedModel.getComponentClassName();
-                    }
-
-                    if (InternalUtils.isBlank(embeddedType)
-                            && InternalUtils.isBlank(embeddedComponentClassName))
-                    {
-                        if (elementName != null)
-                            embeddedType = "Any";
-                        else
-                            throw new TapestryException(ServicesMessages
-                                    .noTypeForEmbeddedComponent(embeddedId, componentClassName),
-                                    token, null);
-                    }
-
-                    ComponentPageElement newComponent = _pageElementFactory.newComponentElement(
-                            _page,
-                            loadingElement,
-                            embeddedId,
-                            embeddedType,
-                            embeddedComponentClassName,
-                            elementName,
-                            startComponent.getLocation());
-
-                    addMixinsToComponent(newComponent, embeddedModel, startComponent.getMixins());
-
-                    add(loadingElement, activeComponent, newComponent);
-
-                    if (embeddedModel != null)
-                        addParametersFromModel(embeddedModel, loadingElement, newComponent);
-
-                    // Remember that we have to load this new component and any of its
-                    // subcomponents, later.
-
-                    push(_componentQueue, newComponent);
-
-                    push(activePageElementStack, activePageElement);
-                    push(activeComponentStack, activeComponent);
-
-                    activePageElement = newComponent;
-                    activeComponent = newComponent;
-
-                    // If we see any attribute tokens immediately following the start
-                    // of a component, they are parameters of the component.
-                    // Remember that attribute token can only directly follow
-                    // start component or start element tokens -- due to the structure of
-                    // XML and SAX, they just can't show up elsewhere.
-
-                    directlyInsideSubcomponent = true;
-
-                    break;
-
-                case ATTRIBUTE:
-
-                    AttributeToken attribute = (AttributeToken) token;
-
-                    if (directlyInsideSubcomponent)
-                    {
-                        addBindingToComponent(loadingElement, activeComponent, attribute);
-                    }
-                    else
-                        add(loadingElement, activeComponent, _pageElementFactory
-                                .newAttributeElement(attribute));
-                    break;
-
-                default:
-                    throw new IllegalStateException("Just haven't written that stuff yet.");
-            }
-
-        }
-    }
-
-    private void addMixinsToComponent(ComponentPageElement component, EmbeddedComponentModel model,
-            String mixins)
-    {
-        if (model != null)
-        {
-            for (String mixinClassName : model.getMixinClassNames())
-                _pageElementFactory.addMixinByClassName(component, mixinClassName);
-        }
-
-        if (mixins != null)
-        {
-            for (String type : mixins.split(","))
-                _pageElementFactory.addMixinByTypeName(component, type);
-        }
-    }
-
-    private void addParametersFromModel(EmbeddedComponentModel model,
-            ComponentPageElement loadingComponent, ComponentPageElement component)
-    {
-        for (String name : model.getParameterNames())
-        {
-            String value = model.getParameterValue(name);
-
-            // At some point we may add meta data to control what the default prefix is within a
-            // component.
-
-            Binding binding = _bindingSource.newBinding(
-                    "parameter " + name,
-                    loadingComponent.getComponentResources(),
-                    component.getComponentResources(),
-                    InternalConstants.PROP_BINDING_PREFIX,
-                    value,
-                    null);
-
-            component.addParameter(name, binding);
-        }
-    }
-
-    private String generateEmbeddedId(String embeddedType, IdAllocator idAllocator)
-    {
-        // TODO: really should trim to last / OR last .
-
-        int x = embeddedType.lastIndexOf("/");
-
-        // The idAllocator is pre-loaded with all the component ids from the template, so even
-        // if the lower-case type matches the id of an existing component, there won't be a name
-        // collision.
-
-        return idAllocator.allocateId(embeddedType.substring(x + 1).toLowerCase());
-    }
-
-    // This is for bindings from the template.
-    private void addBindingToComponent(ComponentPageElement loadingElement,
-            ComponentPageElement component, AttributeToken token)
-    {
-        String name = token.getName();
-        ComponentResources resources = component.getComponentResources();
-
-        // If already bound (i.e., from the component class, via @Component), then
-        // ignore the value in the template. This may need improving to just ignore
-        // the value if it is an unprefixed literal string.
-
-        if (resources.isBound(name))
-            return;
-
-        Binding binding = _bindingSource.newBinding(
-                "parameter " + name,
-                loadingElement.getComponentResources(),
-                component.getComponentResources(),
-                InternalConstants.PROP_BINDING_PREFIX,
-                token.getValue(),
-                token.getLocation());
-
-        component.addParameter(name, binding);
-    }
-
-    private void addRenderBodyElement(ComponentPageElement loadingComponent)
-    {
-        loadingComponent.addToTemplate(_pageElementFactory.newRenderBodyElement(loadingComponent));
-    }
-
-    private void add(ComponentPageElement loadingComponent, ComponentPageElement activeComponent,
-            PageElement child)
-    {
-        if (loadingComponent == activeComponent)
-            activeComponent.addToTemplate(child);
-        else
-            activeComponent.addToBody(child);
-    }
-
-    /** Peeks at the top element on the stack without changing the stack. */
-    static <T> T peek(List<T> stack)
-    {
-        int size = stack.size();
-
-        if (size == 0)
-            throw new IllegalStateException("Stack is empty.");
-
-        return stack.get(size - 1);
-    }
-
-    /** Pops the top element off the stack and returns it. */
-    static <T> T pop(List<T> stack)
-    {
-        int size = stack.size();
-
-        if (size == 0)
-            throw new IllegalStateException("Stack is empty.");
-
-        return stack.remove(size - 1);
-    }
-
-    /** Pushes a new element onto the top of the stack. */
-    static <T> void push(List<T> stack, T element)
-    {
-        stack.add(element);
+        return processor.loadPage(pageClassName, locale);
     }
 
     /**

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java Thu Jan 11 17:57:38 2007
@@ -0,0 +1,543 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.internal.services;
+
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newLinkedList;
+import static org.apache.tapestry.ioc.internal.util.InternalUtils.isBlank;
+import static org.apache.tapestry.ioc.internal.util.InternalUtils.isNonBlank;
+
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.Binding;
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.internal.InternalConstants;
+import org.apache.tapestry.internal.parser.AttributeToken;
+import org.apache.tapestry.internal.parser.BodyToken;
+import org.apache.tapestry.internal.parser.CommentToken;
+import org.apache.tapestry.internal.parser.ComponentTemplate;
+import org.apache.tapestry.internal.parser.EndElementToken;
+import org.apache.tapestry.internal.parser.ExpansionToken;
+import org.apache.tapestry.internal.parser.StartComponentToken;
+import org.apache.tapestry.internal.parser.StartElementToken;
+import org.apache.tapestry.internal.parser.TemplateToken;
+import org.apache.tapestry.internal.parser.TextToken;
+import org.apache.tapestry.internal.structure.BodyPageElement;
+import org.apache.tapestry.internal.structure.ComponentPageElement;
+import org.apache.tapestry.internal.structure.Page;
+import org.apache.tapestry.internal.structure.PageElement;
+import org.apache.tapestry.internal.structure.PageImpl;
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry.ioc.internal.util.IdAllocator;
+import org.apache.tapestry.ioc.internal.util.OneShotLock;
+import org.apache.tapestry.ioc.internal.util.TapestryException;
+import org.apache.tapestry.model.ComponentModel;
+import org.apache.tapestry.model.EmbeddedComponentModel;
+import org.apache.tapestry.services.BindingSource;
+import org.apache.tapestry.services.PersistentFieldManager;
+
+/**
+ * Contains all the work-state related to the {@link PageLoaderImpl}.
+ */
+class PageLoaderProcessor
+{
+    private static Runnable NO_OP = new Runnable()
+    {
+        public void run()
+        {
+            // Do nothing.
+        }
+    };
+
+    private LinkedList<ComponentPageElement> _activeElementStack = newLinkedList();
+
+    private boolean _addAttributesAsComponentBindings = false;
+
+    private final BindingSource _bindingSource;
+
+    private final LinkedList<BodyPageElement> _bodyPageElementStack = newLinkedList();
+
+    private final LinkedList<ComponentPageElement> _componentQueue = newLinkedList();
+
+    private final LinkedList<Boolean> _discardEndTagStack = newLinkedList();
+
+    private final LinkedList<Runnable> _endElementStack = newLinkedList();
+
+    private final IdAllocator _idAllocator = new IdAllocator();
+
+    private final LinkFactory _linkFactory;
+
+    private ComponentModel _loadingComponentModel;
+
+    private ComponentPageElement _loadingElement;
+
+    private Locale _locale;
+
+    private final OneShotLock _lock = new OneShotLock();
+
+    private Page _page;
+
+    private final PageElementFactory _pageElementFactory;
+
+    private final PersistentFieldManager _persistentFieldManager;
+
+    private final ComponentTemplateSource _templateSource;
+
+    public PageLoaderProcessor(ComponentTemplateSource templateSource,
+            PageElementFactory pageElementFactory, BindingSource bindingSource,
+            LinkFactory linkFactory, PersistentFieldManager persistentFieldManager)
+    {
+        _templateSource = templateSource;
+        _pageElementFactory = pageElementFactory;
+        _bindingSource = bindingSource;
+        _linkFactory = linkFactory;
+        _persistentFieldManager = persistentFieldManager;
+    }
+
+    // This is for bindings from the template.
+    private void addBindingToComponent(ComponentPageElement component, AttributeToken token)
+    {
+        String name = token.getName();
+        ComponentResources resources = component.getComponentResources();
+
+        // If already bound (i.e., from the component class, via @Component), then
+        // ignore the value in the template. This may need improving to just ignore
+        // the value if it is an unprefixed literal string.
+
+        if (resources.isBound(name))
+            return;
+
+        Binding binding = _bindingSource.newBinding(
+                "parameter " + name,
+                _loadingElement.getComponentResources(),
+                component.getComponentResources(),
+                InternalConstants.PROP_BINDING_PREFIX,
+                token.getValue(),
+                token.getLocation());
+
+        component.addParameter(name, binding);
+    }
+
+    // As element, components, parameters or blocks are started, they push an element onto this
+    // stack. Whenever an end element token is reached, the top value is popped off and executed,
+    // to return state to where it should be.
+
+    private void addMixinsToComponent(ComponentPageElement component, EmbeddedComponentModel model,
+            String mixins)
+    {
+        if (model != null)
+        {
+            for (String mixinClassName : model.getMixinClassNames())
+                _pageElementFactory.addMixinByClassName(component, mixinClassName);
+        }
+
+        if (mixins != null)
+        {
+            for (String type : mixins.split(","))
+                _pageElementFactory.addMixinByTypeName(component, type);
+        }
+    }
+
+    private void addParametersFromModel(EmbeddedComponentModel model,
+            ComponentPageElement loadingComponent, ComponentPageElement component)
+    {
+        for (String name : model.getParameterNames())
+        {
+            String value = model.getParameterValue(name);
+
+            // At some point we may add meta data to control what the default prefix is within a
+            // component.
+
+            Binding binding = _bindingSource.newBinding(
+                    "parameter " + name,
+                    loadingComponent.getComponentResources(),
+                    component.getComponentResources(),
+                    InternalConstants.PROP_BINDING_PREFIX,
+                    value,
+                    null);
+
+            component.addParameter(name, binding);
+        }
+    }
+
+    private void addRenderBodyElement()
+    {
+        PageElement element = _pageElementFactory.newRenderBodyElement(_loadingElement);
+
+        _loadingElement.addToTemplate(element);
+    }
+
+    private void addToBody(PageElement element)
+    {
+        _bodyPageElementStack.peek().addToBody(element);
+    }
+
+    private void attribute(AttributeToken token)
+    {
+        // This kind of bookkeeping is ugly, we probably should have distinct (if very similar)
+        // tokens for attributes and for parameter bindings.
+
+        if (_addAttributesAsComponentBindings)
+        {
+            ComponentPageElement activeElement = _activeElementStack.peek();
+
+            addBindingToComponent(activeElement, token);
+            return;
+        }
+
+        PageElement element = _pageElementFactory.newAttributeElement(token);
+
+        addToBody(element);
+    }
+
+    private void body(BodyToken token)
+    {
+        addRenderBodyElement();
+
+        // BODY tokens are *not* matched by END_ELEMENT tokens. Nor will there be
+        // text or comment content "inside" the BODY.
+    }
+
+    private void comment(CommentToken token)
+    {
+        PageElement commentElement = _pageElementFactory.newCommentElement(token);
+
+        addToBody(commentElement);
+    }
+
+    private void endElement(EndElementToken token)
+    {
+        // discard will be false if the matching start token was for a static element, and will be
+        // true otherwise (component, block, parameter).
+
+        boolean discard = _discardEndTagStack.removeFirst();
+
+        if (!discard)
+        {
+            PageElement element = _pageElementFactory.newEndElement();
+
+            addToBody(element);
+        }
+
+        Runnable runnable = _endElementStack.removeFirst();
+
+        // Used to return environment to prior state.
+
+        runnable.run();
+    }
+
+    private void expansion(ExpansionToken token)
+    {
+        PageElement element = _pageElementFactory.newExpansionElement(_loadingElement
+                .getComponentResources(), token);
+
+        addToBody(element);
+    }
+
+    private String generateEmbeddedId(String embeddedType, IdAllocator idAllocator)
+    {
+        // Component types may be in folders; strip off the folder part for starters.
+
+        int slashx = embeddedType.lastIndexOf("/");
+
+        String baseId = embeddedType.substring(slashx + 1).toLowerCase();
+
+        // The idAllocator is pre-loaded with all the component ids from the template, so even
+        // if the lower-case type matches the id of an existing component, there won't be a name
+        // collision.
+
+        return idAllocator.allocateId(baseId);
+    }
+
+    /**
+     * As currently implemented, this should be invoked just once and then the instance should be
+     * discarded.
+     */
+    public Page loadPage(String pageClassName, Locale locale)
+    {
+        // Ensure that loadPage() may only be invoked once.
+
+        _lock.lock();
+
+        _locale = locale;
+
+        _page = new PageImpl(pageClassName, _locale, _linkFactory, _persistentFieldManager);
+
+        loadRootComponent(pageClassName);
+
+        workComponentQueue();
+
+        // The page is *loaded* before it is attached to the request.
+        // This is to help ensure that no client-specific information leaks
+        // into the page.
+
+        _page.loaded();
+
+        return _page;
+    }
+
+    private void loadRootComponent(String className)
+    {
+        ComponentPageElement rootComponent = _pageElementFactory.newRootComponentElement(
+                _page,
+                className);
+
+        _page.setRootElement(rootComponent);
+
+        _componentQueue.addFirst(rootComponent);
+    }
+
+    /**
+     * Do you smell something? I'm smelling that this class needs to be redesigned to not need a
+     * central method this large and hard to test. I think a lot of instance and local variables
+     * need to be bundled up into some kind of process object. This code is effectively too big to
+     * be tested except through integration testing.
+     */
+    private void loadTemplateForComponent(ComponentPageElement loadingElement)
+    {
+        _loadingElement = loadingElement;
+        _loadingComponentModel = loadingElement.getComponentResources().getComponentModel();
+
+        String componentClassName = _loadingComponentModel.getComponentClassName();
+        ComponentTemplate template = _templateSource.getTemplate(_loadingComponentModel, _locale);
+
+        // When the template for a component is missing, we pretend it consists of just a RenderBody
+        // phase. Missing is not an error ... many component simply do not have a template.
+
+        if (template.isMissing())
+        {
+            addRenderBodyElement();
+            return;
+        }
+
+        // Pre-allocate ids to avoid later name collisions.
+
+        Log log = _loadingComponentModel.getLog();
+
+        Set<String> embeddedIds = CollectionFactory.newSet(_loadingComponentModel
+                .getEmbeddedComponentIds());
+
+        _idAllocator.clear();
+
+        for (String id : template.getComponentIds())
+        {
+            _idAllocator.allocateId(id);
+            embeddedIds.remove(id);
+        }
+
+        if (!embeddedIds.isEmpty())
+            log.error(ServicesMessages.embeddedComponentsNotInTemplate(
+                    embeddedIds,
+                    componentClassName));
+
+        _addAttributesAsComponentBindings = false;
+
+        // The outermost elements of the template belong in the loading component's template list,
+        // not its body list. This shunt allows everyone else to not have to make that decision,
+        // they can add to the "body" and (if there isn't an active component), the shunt will
+        // add the element to the component's template.
+
+        BodyPageElement shunt = new BodyPageElement()
+        {
+            public void addToBody(PageElement element)
+            {
+                _loadingElement.addToTemplate(element);
+            }
+        };
+
+        _bodyPageElementStack.addFirst(shunt);
+
+        for (TemplateToken token : template.getTokens())
+        {
+            switch (token.getTokenType())
+            {
+                case TEXT:
+                    text((TextToken) token);
+                    break;
+
+                case EXPANSION:
+                    expansion((ExpansionToken) token);
+                    break;
+
+                case BODY:
+                    body((BodyToken) token);
+                    break;
+
+                case START_ELEMENT:
+                    startElement((StartElementToken) token);
+                    break;
+
+                case START_COMPONENT:
+                    startComponent((StartComponentToken) token);
+                    break;
+
+                case ATTRIBUTE:
+                    attribute((AttributeToken) token);
+                    break;
+
+                case END_ELEMENT:
+                    endElement((EndElementToken) token);
+                    break;
+
+                case COMMENT:
+                    comment((CommentToken) token);
+                    break;
+
+                default:
+                    throw new IllegalStateException("Not implemented yet: " + token);
+            }
+        }
+
+        // For neatness / symmetry:
+
+        _bodyPageElementStack.removeFirst(); // the shunt
+
+        // TODO: Check that all stacks are empty. That should never happen, as long
+        // as the ComponentTemplate is valid.
+    }
+
+    private void startComponent(StartComponentToken token)
+    {
+        String elementName = token.getElementName();
+
+        // Initial guess: the type from the token (but this may be null in many cases).
+        String embeddedType = token.getType();
+
+        String embeddedId = token.getId();
+
+        String embeddedComponentClassName = null;
+
+        // We know that if embeddedId is null, embeddedType is not.
+
+        if (embeddedId == null)
+            embeddedId = generateEmbeddedId(embeddedType, _idAllocator);
+
+        EmbeddedComponentModel embeddedModel = _loadingComponentModel
+                .getEmbeddedComponentModel(embeddedId);
+
+        if (embeddedModel != null)
+        {
+            String modelType = embeddedModel.getComponentType();
+
+            if (isNonBlank(modelType) && embeddedType != null)
+            {
+                Log log = _loadingComponentModel.getLog();
+                log.error(ServicesMessages.compTypeConflict(embeddedId, embeddedType, modelType));
+            }
+
+            embeddedType = modelType;
+            embeddedComponentClassName = embeddedModel.getComponentClassName();
+        }
+
+        if (isBlank(embeddedType) && isBlank(embeddedComponentClassName))
+        {
+            // non-null means its invisible instrumentation; the Any component
+            // will mimic the actual element, w/ body and informal parameters.
+
+            if (elementName != null)
+                embeddedType = "Any";
+            else
+                throw new TapestryException(ServicesMessages.noTypeForEmbeddedComponent(
+                        embeddedId,
+                        _loadingComponentModel.getComponentClassName()), token, null);
+        }
+
+        ComponentPageElement newComponent = _pageElementFactory.newComponentElement(
+                _page,
+                _loadingElement,
+                embeddedId,
+                embeddedType,
+                embeddedComponentClassName,
+                elementName,
+                token.getLocation());
+
+        addMixinsToComponent(newComponent, embeddedModel, token.getMixins());
+
+        if (embeddedModel != null)
+            addParametersFromModel(embeddedModel, _loadingElement, newComponent);
+
+        addToBody(newComponent);
+
+        _componentQueue.addFirst(newComponent);
+
+        // Any attribute tokens that immediately follow should be
+        // used to bind parameters.
+
+        _addAttributesAsComponentBindings = true;
+
+        // Any attributes or parameters that come up belong on this component.
+
+        _activeElementStack.addFirst(newComponent);
+
+        // Set things up so that content inside the component is added to the component's body.
+
+        _bodyPageElementStack.addFirst(newComponent);
+
+        // The start tag is not added to the body of the component, so neither should
+        // the end tag.
+
+        _discardEndTagStack.addFirst(true);
+
+        // And clean that up when the end element is reached.
+
+        Runnable cleanup = new Runnable()
+        {
+            public void run()
+            {
+                _activeElementStack.removeFirst();
+                _bodyPageElementStack.removeFirst();
+            }
+        };
+
+        _endElementStack.addFirst(cleanup);
+    }
+
+    private void startElement(StartElementToken token)
+    {
+        PageElement element = _pageElementFactory.newStartElement(token);
+
+        addToBody(element);
+
+        // Controls how attributes are interpretted.
+        _addAttributesAsComponentBindings = false;
+
+        // Start will be matched by end:
+
+        // Do NOT discard the end tag; add it to the body.
+
+        _discardEndTagStack.addFirst(false);
+        _endElementStack.addFirst(NO_OP);
+    }
+
+    private void text(TextToken token)
+    {
+        PageElement element = _pageElementFactory.newTextElement(token);
+
+        addToBody(element);
+    }
+
+    /** Works the component queue, until exausted. */
+    private void workComponentQueue()
+    {
+        while (!_componentQueue.isEmpty())
+        {
+            ComponentPageElement componentElement = _componentQueue.removeFirst();
+
+            loadTemplateForComponent(componentElement);
+        }
+    }
+}

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=495465&r1=495464&r2=495465
==============================================================================
--- 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 Jan 11 17:57:38 2007
@@ -325,4 +325,24 @@
                 .getCompleteId(), result, methodDescription, ClassFabUtils.getJavaClassName(result
                 .getClass()), InternalUtils.joinSorted(classNames));
     }
+
+    static String undefinedTapestryElement(String elementName)
+    {
+        return MESSAGES.format("undefined-tapestry-element", elementName);
+    }
+
+    static String undefinedTapestryAttribute(String elementName, String attributeName,
+            String allowedAttributeName)
+    {
+        return MESSAGES.format(
+                "undefined-tapestry-attribute",
+                elementName,
+                attributeName,
+                allowedAttributeName);
+    }
+
+    static String parameterElementNameRequired()
+    {
+        return MESSAGES.get("parameter-element-name-required");
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java?view=diff&rev=495465&r1=495464&r2=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/TemplateParserImpl.java Thu Jan 11 17:57:38 2007
@@ -25,6 +25,7 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.tapestry.internal.parser.AttributeToken;
+import org.apache.tapestry.internal.parser.BlockToken;
 import org.apache.tapestry.internal.parser.BodyToken;
 import org.apache.tapestry.internal.parser.CDATAToken;
 import org.apache.tapestry.internal.parser.CommentToken;
@@ -32,6 +33,7 @@
 import org.apache.tapestry.internal.parser.ComponentTemplateImpl;
 import org.apache.tapestry.internal.parser.EndElementToken;
 import org.apache.tapestry.internal.parser.ExpansionToken;
+import org.apache.tapestry.internal.parser.ParameterToken;
 import org.apache.tapestry.internal.parser.StartComponentToken;
 import org.apache.tapestry.internal.parser.StartElementToken;
 import org.apache.tapestry.internal.parser.TemplateToken;
@@ -277,11 +279,10 @@
 
         if (TAPESTRY_SCHEMA_5_0_0.equals(uri))
         {
-            startTapestryElement(localName, attributes);
+            startTapestryElement(qName, localName, attributes);
             return;
         }
 
-        // TODO: Handle tapestry namespace attributes in ordinary namespace elements?
         // TODO: Handle interpolations inside attributes?
 
         startStaticElement(localName, attributes);
@@ -387,11 +388,11 @@
         return _insideBody;
     }
 
-    private void startTapestryElement(String localName, Attributes attributes)
+    private void startTapestryElement(String qname, String localName, Attributes attributes)
     {
         if (localName.equals("comp"))
         {
-            startComponent(localName, attributes);
+            startComponent(attributes);
             return;
         }
 
@@ -401,10 +402,69 @@
             return;
         }
 
-        throw new IllegalArgumentException("Unknown localName: '" + localName + "'.");
+        if (localName.equals("parameter"))
+        {
+            startParameter(attributes);
+            return;
+        }
+
+        if (localName.equals("block"))
+        {
+            startBlock(attributes);
+            return;
+        }
+
+        throw new TapestryException(ServicesMessages.undefinedTapestryElement(qname),
+                getCurrentLocation(), null);
     }
 
-    private void startComponent(String localName, Attributes attributes)
+    private void startBlock(Attributes attributes)
+    {
+        String blockId = findSingleParameter("block", "id", attributes);
+
+        // null is ok for blockId
+
+        _tokens.add(new BlockToken(blockId, getCurrentLocation()));
+    }
+
+    private void startParameter(Attributes attributes)
+    {
+        String parameterName = findSingleParameter("parameter", "name", attributes);
+
+        if (InternalUtils.isBlank(parameterName))
+            throw new TapestryException(ServicesMessages.parameterElementNameRequired(),
+                    getCurrentLocation(), null);
+
+        _tokens.add(new ParameterToken(parameterName, getCurrentLocation()));
+    }
+
+    private String findSingleParameter(String elementName, String attributeName,
+            Attributes attributes)
+    {
+        String result = null;
+
+        for (int i = 0; i < attributes.getLength(); i++)
+        {
+            String name = attributes.getLocalName(i);
+
+            if (name.equals(attributeName))
+            {
+                result = attributes.getValue(i);
+                continue;
+            }
+
+            // Only the name attribute is allowed.
+
+            throw new TapestryException(ServicesMessages.undefinedTapestryAttribute(
+                    elementName,
+                    name,
+                    attributeName), getCurrentLocation(), null);
+        }
+
+        return result;
+    }
+
+    private void startComponent(Attributes attributes)
     {
         String id = null;
         String type = null;
@@ -501,8 +561,8 @@
 
         processTextBuffer();
 
-        // Remove excess whitespace.  The Comment DOM node will add a leadig and trailing space.
-        
+        // Remove excess whitespace. The Comment DOM node will add a leadig and trailing space.
+
         String comment = new String(ch, start, length).trim();
 
         // TODO: Perhaps comments need to be "aggregated" the same way we aggregate text and CDATA.

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BlockImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BlockImpl.java?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BlockImpl.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BlockImpl.java Thu Jan 11 17:57:38 2007
@@ -0,0 +1,55 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.internal.structure;
+
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
+
+import java.util.List;
+
+import org.apache.tapestry.Block;
+import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.ioc.BaseLocatable;
+import org.apache.tapestry.ioc.Location;
+import org.apache.tapestry.runtime.RenderCommand;
+import org.apache.tapestry.runtime.RenderQueue;
+
+public class BlockImpl extends BaseLocatable implements Block, BodyPageElement, RenderCommand
+{
+    // We could lazily create this, but for <t:block> and <t:parameter>, the case
+    // for an empty block is extremely rare.
+
+    private final List<PageElement> _elements = newList();
+
+    public BlockImpl(Location location)
+    {
+        super(location);
+    }
+
+    public void addToBody(PageElement element)
+    {
+        _elements.add(element);
+    }
+
+    /**
+     * Pushes all the elements of the body of this block onto the queue in appropriate order.
+     */
+    public void render(MarkupWriter writer, RenderQueue queue)
+    {
+        int count = _elements.size();
+        for (int i = count - 1; i >= 0; i--)
+            queue.push(_elements.get(i));
+    }
+
+}

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BodyPageElement.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BodyPageElement.java?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BodyPageElement.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/BodyPageElement.java Thu Jan 11 17:57:38 2007
@@ -0,0 +1,31 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.internal.structure;
+
+import org.apache.tapestry.internal.services.PageLoader;
+
+/**
+ * A type of {@link PageElement} that has a body that can be added to. This is part of the
+ * constuction phase that is faciliated by the {@link PageLoader}.
+ */
+public interface BodyPageElement
+{
+    /**
+     * Used during the construction of the page. Adds a page element as part of the body of the
+     * component. The body of a component is defined as the portion of the container's template
+     * directly enclosed by component's start and end elements.
+     */
+    void addToBody(PageElement element);
+}

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=495465&r1=495464&r2=495465
==============================================================================
--- 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 Jan 11 17:57:38 2007
@@ -12,94 +12,85 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.internal.structure;
-
-import org.apache.tapestry.ComponentResourcesCommon;
-import org.apache.tapestry.internal.InternalComponentResources;
-import org.apache.tapestry.internal.InternalComponentResourcesCommon;
-import org.apache.tapestry.internal.services.Instantiator;
-import org.apache.tapestry.runtime.ComponentEvent;
-import org.apache.tapestry.runtime.Component;
-import org.apache.tapestry.runtime.RenderCommand;
-import org.apache.tapestry.runtime.RenderQueue;
-
-/**
- * Extended version of {@link org.apache.tapestry.internal.structure.PageElement} for elements that
- * are, in fact, components (rather than just static markup).
- */
-public interface ComponentPageElement extends ComponentResourcesCommon,
-        InternalComponentResourcesCommon, PageElement, RenderCommand
-{
-    /**
-     * Returns the core component associated with this page element (as opposed to any mixins
-     * attached to the component).
-     */
-    Component getComponent();
-
-    /** Returns the resources associated with the core component. */
-    InternalComponentResources getComponentResources();
-
-    /** Returns the page which contains this component. */
-    Page getContainingPage();
-
-    /**
-     * Containing component (or null for the root component of a page).
-     */
-    ComponentPageElement getContainerElement();
-
-    /**
-     * Used during the construction of a page. Adds a page element as part of the template for this
-     * page element. A page element will eventually render by sequentially rendering these elements.
-     * A page elements template is really just the outermost portions of the component's template
-     * ... where a template contains elements that are reall components, those components will
-     * receive portions of the template as their body.
-     */
-
-    void addToTemplate(PageElement element);
-
-    /**
-     * Used during the construction of the page. Adds a page element as part of the body of the
-     * component. The body of a component is defined as the portion of the container's template
-     * directly enclosed by component's start and end elements.
-     */
-    void addToBody(PageElement element);
-
-    /**
-     * Adds a component to its container. The embedded component's id must be unique within the
-     * container.
-     */
-    void addEmbeddedElement(ComponentPageElement child);
-
-    /**
-     * Adds a mixin.
-     * 
-     * @param instantiator
-     *            used to instantiate an instance of the mixin
-     */
-    void addMixin(Instantiator instantiator);
-
-    /**
-     * Retrieves a component page element by its id.
-     * 
-     * @param id
-     *            used to locate the element
-     * @return the page element
-     * @throws IllegalArgumentException
-     *             if no component exists with the given id
-     */
-    ComponentPageElement getEmbeddedElement(String id);
-
-    /** Invoked when the component should render its body. */
-    void enqueueBeforeRenderBody(RenderQueue queue);
-
-    /**
-     * Asks each mixin and component to
-     * {@link Component#handleComponentEvent(ComponentEvent)}, returning true if any
-     * handler was found.
-     * 
-     * @param event
-     *            to be handled
-     * @return true if a handler was found
-     */
-    boolean handleEvent(ComponentEvent event);
-}
+package org.apache.tapestry.internal.structure;
+
+import org.apache.tapestry.ComponentResourcesCommon;
+import org.apache.tapestry.internal.InternalComponentResources;
+import org.apache.tapestry.internal.InternalComponentResourcesCommon;
+import org.apache.tapestry.internal.services.Instantiator;
+import org.apache.tapestry.runtime.Component;
+import org.apache.tapestry.runtime.ComponentEvent;
+import org.apache.tapestry.runtime.RenderQueue;
+
+/**
+ * Extended version of {@link org.apache.tapestry.internal.structure.PageElement} for elements that
+ * are, in fact, components (rather than just static markup).
+ */
+public interface ComponentPageElement extends ComponentResourcesCommon,
+        InternalComponentResourcesCommon, PageElement, BodyPageElement
+{
+    /**
+     * Returns the core component associated with this page element (as opposed to any mixins
+     * attached to the component).
+     */
+    Component getComponent();
+
+    /** Returns the resources associated with the core component. */
+    InternalComponentResources getComponentResources();
+
+    /** Returns the page which contains this component. */
+    Page getContainingPage();
+
+    /**
+     * Containing component (or null for the root component of a page).
+     */
+    ComponentPageElement getContainerElement();
+
+    /**
+     * Used during the construction of a page. Adds a page element as part of the template for this
+     * page element. A page element will eventually render by sequentially rendering these elements.
+     * A page elements template is really just the outermost portions of the component's template
+     * ... where a template contains elements that are all components, those components will receive
+     * portions of the template as their body.
+     */
+
+    void addToTemplate(PageElement element);
+
+    /**
+     * Adds a component to its container. The embedded component's id must be unique within the
+     * container.
+     */
+    void addEmbeddedElement(ComponentPageElement child);
+
+    /**
+     * Adds a mixin.
+     * 
+     * @param instantiator
+     *            used to instantiate an instance of the mixin
+     */
+    void addMixin(Instantiator instantiator);
+
+    /**
+     * Retrieves a component page element by its id.
+     * 
+     * @param id
+     *            used to locate the element
+     * @return the page element
+     * @throws IllegalArgumentException
+     *             if no component exists with the given id
+     */
+    ComponentPageElement getEmbeddedElement(String id);
+
+    /** Invoked when the component should render its body. */
+    void enqueueBeforeRenderBody(RenderQueue queue);
+
+    /**
+     * Asks each mixin and component to {@link Component#handleComponentEvent(ComponentEvent)},
+     * returning true if any handler was found.
+     * 
+     * @param event
+     *            to be handled
+     * @return true if a handler was found
+     */
+    boolean handleEvent(ComponentEvent event);
+}

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=495465&r1=495464&r2=495465
==============================================================================
--- 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 Jan 11 17:57:38 2007
@@ -69,4 +69,7 @@
 validator-specification-parse-error=Unexpected character '%s' at position %d of input string: %s
 unknown-translator-type=Unknown translator type '%s'.  Configured translators are %s.
 missing-from-environment=No object of type %s is available from the Environment.  Available types are %s.
-invalid-component-event-result=An event handler for component %s returned the value %s (from method %s).  Return type %s can not be handled.  Configured return types are %s.
\ No newline at end of file
+invalid-component-event-result=An event handler for component %s returned the value %s (from method %s).  Return type %s can not be handled.  Configured return types are %s.
+undefined-tapestry-element=Element <%s> is in the Tapestry namespace, but is not a recognized Tapestry template element.
+undefined-tapestry-attribute=Element <%s> does not support an attribute named '%s'. The only allowed attribute name is '%s'.
+parameter-element-name-required=The name attribute of the <parameter> element must be specified.
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/tapestry_5_0_0.xsd
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/tapestry_5_0_0.xsd?view=diff&rev=495465&r1=495464&r2=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/tapestry_5_0_0.xsd (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/tapestry_5_0_0.xsd Thu Jan 11 17:57:38 2007
@@ -2,14 +2,89 @@
 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
     xmlns="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd"
     targetNamespace="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
-    <!-- comp is a component within the template -->
     <xs:element name="comp">
-        <xs:complexType mixed="true">
-            <xs:attribute name="id" type="xs:ID"/>
-            <xs:attribute name="type" type="xs:string"/>
-            <xs:anyAttribute/>
-        </xs:complexType>
+        <xs:annotation>
+            <xs:documentation> A component within the template. Allows any attributes, these are
+                bound to parameters of the component. A component will have either an id, a type, or
+                both (they are both optional, with the requirement for one or the other implemented
+                internally). </xs:documentation>
+        </xs:annotation>
+        <xs:complexType mixed="true"> 
+            <xs:attribute name="id" type="xs:ID">
+                <xs:annotation>
+                    <xs:documentation> A unique identifier for the component within the template.  If the type
+                        is omitted, Tapestry will provide a unique id for the component.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="type" type="xs:string">
+                <xs:annotation>
+                    <xs:documentation>
+                        The name of a component type. This is optional, as the type may be determined elsewhere
+                        or otherwise defaulted by Tapestry.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="mixins" type="xs:string">
+                <xs:annotation>
+                    <xs:documentation>
+                        A comma-seperated list of mixin type names.  Mixins are optional and provide additional
+                        functionality to the base component.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+            <xs:anyAttribute>
+                <xs:annotation>
+                    <xs:documentation>
+                        Any additional attributes are used to bind parameters of the component (or of mixins of the component).
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:anyAttribute>
+        </xs:complexType>        
     </xs:element>
     <!-- body identifies where the body of the component belongs within the component's template -->
-    <xs:element name="body"/>
+    <xs:element name="body">
+        <xs:annotation>
+            <xs:documentation>
+                Defines the position within the template that the body of the component (the portion of the container's template
+                enclosed by the component) will be rendered.  This is optional, and only applies to components that wish to render 
+                their body within their template.
+            </xs:documentation>
+        </xs:annotation>
+    </xs:element>
+    <xs:element name="parameter">
+        <xs:annotation>
+            <xs:documentation>
+                A structured parameter passed to a component as a single object of type Block.  The receiving component
+                can get the Block to render. A parameter should always be enclosed by a component element
+                (either an explicit comp element, or an ordinary element instrumented with a Tapestry type or id).
+            </xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+            <xs:attribute name="name" type="xs:string" use="required">
+                <xs:annotation>
+                    <xs:documentation>
+                        The name of the parameter to be bound to the Block.
+                    </xs:documentation>
+                </xs:annotation>                
+            </xs:attribute>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="block">
+        <xs:annotation>
+            <xs:documentation>
+                A block is simply a container of other elements.  Blocks do not render themselves or their bodies normally, unless
+                specifically directed to.
+            </xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+            <xs:attribute name="id" type="xs:ID">
+                <xs:annotation>
+                    <xs:documentation>
+                        An optional identifier that is used to reference the block from inside the Java class.
+                    </xs:documentation>
+                </xs:annotation>
+            </xs:attribute>
+        </xs:complexType>
+    </xs:element>
 </xs:schema>

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/TemplateParserImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/TemplateParserImplTest.java?view=diff&rev=495465&r1=495464&r2=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/TemplateParserImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/TemplateParserImplTest.java Thu Jan 11 17:57:38 2007
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry.internal.services;
 
+import static java.lang.String.format;
 import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newSet;
 import static org.easymock.EasyMock.contains;
 
@@ -23,22 +24,26 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.tapestry.internal.parser.AttributeToken;
+import org.apache.tapestry.internal.parser.BlockToken;
 import org.apache.tapestry.internal.parser.BodyToken;
 import org.apache.tapestry.internal.parser.CDATAToken;
 import org.apache.tapestry.internal.parser.CommentToken;
 import org.apache.tapestry.internal.parser.ComponentTemplate;
 import org.apache.tapestry.internal.parser.EndElementToken;
 import org.apache.tapestry.internal.parser.ExpansionToken;
+import org.apache.tapestry.internal.parser.ParameterToken;
 import org.apache.tapestry.internal.parser.StartComponentToken;
 import org.apache.tapestry.internal.parser.StartElementToken;
 import org.apache.tapestry.internal.parser.TemplateToken;
 import org.apache.tapestry.internal.parser.TextToken;
+import org.apache.tapestry.internal.parser.TokenType;
 import org.apache.tapestry.ioc.Locatable;
 import org.apache.tapestry.ioc.Location;
 import org.apache.tapestry.ioc.Resource;
 import org.apache.tapestry.ioc.internal.util.ClasspathResource;
 import org.apache.tapestry.ioc.internal.util.TapestryException;
 import org.apache.tapestry.test.TapestryTestCase;
+import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
 /**
@@ -328,24 +333,6 @@
     }
 
     @Test
-    public void mixin_requires_id_or_type()
-    {
-        try
-        {
-            tokens("mixin_requires_id_or_type.html");
-            unreachable();
-        }
-        catch (TapestryException ex)
-        {
-            assertTrue(ex
-                    .getMessage()
-                    .contains(
-                            "You may not specify mixins for element <span> because it does not represent a component (which requires either an id attribute or a type attribute)."));
-            assertNotNull(ex.getLocation());
-        }
-    }
-
-    @Test
     void body_element()
     {
         List<TemplateToken> tokens = tokens("body_element.html");
@@ -359,22 +346,6 @@
     }
 
     @Test
-    void illegal_nesting_within_body_element()
-    {
-        try
-        {
-            tokens("illegal_nesting_within_body_element.html");
-            unreachable();
-        }
-        catch (TapestryException ex)
-        {
-            assertTrue(ex.getMessage().contains(
-                    "Element 'xyz' is nested within a Tapestry body element"));
-            assertEquals(ex.getLocation().getLine(), 2);
-        }
-    }
-
-    @Test
     void content_within_body_element()
     {
         Log log = newLog();
@@ -458,21 +429,6 @@
     }
 
     @Test
-    public void component_without_id_or_type()
-    {
-        try
-        {
-            tokens("component_without_id_or_type.html");
-            unreachable();
-        }
-        catch (TapestryException ex)
-        {
-            assertEquals(ex.getCause().getMessage(), ServicesMessages.compRequiresIdOrType());
-            assertEquals(ex.getLocation().getLine(), 2);
-        }
-    }
-
-    @Test
     public void component_ids()
     {
         Log log = newLog();
@@ -574,5 +530,91 @@
 
         assertEquals(t1.getName(), "exp");
         assertEquals(t1.getValue(), "${not-an-expansion}");
+    }
+
+    @Test
+    public void parameter_element()
+    {
+        List<TemplateToken> tokens = tokens("parameter_element.html");
+
+        ParameterToken token4 = get(tokens, 4);
+        assertEquals(token4.getName(), "fred");
+
+        CommentToken token6 = get(tokens, 6);
+        assertEquals(token6.getComment(), "fred content");
+
+        TemplateToken token8 = get(tokens, 8);
+        
+        assertEquals(token8.getTokenType(), TokenType.END_ELEMENT);
+    }
+
+    @Test
+    public void block_element()
+    {
+        List<TemplateToken> tokens = tokens("block_element.html");
+
+        BlockToken token2 = get(tokens, 2);
+        assertEquals(token2.getId(), "block0");
+
+        CommentToken token4 = get(tokens, 4);
+        assertEquals(token4.getComment(), "block0 content");
+
+        BlockToken token8 = get(tokens, 8);
+        assertNull(token8.getId());
+
+        CommentToken token10 = get(tokens, 10);
+        assertEquals(token10.getComment(), "anon block content");
+    }
+
+    @DataProvider(name = "parse_failure_data")
+    public Object[][] parse_failure_data()
+    {
+        return new Object[][]
+        {
+                {
+                        "mixin_requires_id_or_type.html",
+                        "You may not specify mixins for element <span> because it does not represent a component (which requires either an id attribute or a type attribute).",
+                        2 },
+                { "illegal_nesting_within_body_element.html",
+                        "Element 'xyz' is nested within a Tapestry body element", 2 },
+                { "component_without_id_or_type.html", ServicesMessages.compRequiresIdOrType(), 2 },
+                {
+                        "unexpected_element_in_tapestry_namespace.html",
+                        "Element <t:fubar> is in the Tapestry namespace, but is not a recognized Tapestry template element.",
+                        3 },
+                {
+                        "unexpected_attribute_in_parameter_element.html",
+                        "Element <parameter> does not support an attribute named 'grok'. The only allowed attribute name is 'name'.",
+                        4 },
+                { "name_attribute_of_parameter_element_omitted.html",
+                        "The name attribute of the <parameter> element must be specified.", 4 },
+                { "name_attribute_of_parameter_element_blank.html",
+                        "The name attribute of the <parameter> element must be specified.", 4 },
+                {
+                        "unexpected_attribute_in_block_element.html",
+                        "Element <block> does not support an attribute named 'name'. The only allowed attribute name is 'id'.",
+                        3 },
+
+        };
+    }
+
+    @Test(dataProvider = "parse_failure_data")
+    public void parse_failure(String fileName, String errorMessageSubstring, int expectedLine)
+    {
+        try
+        {
+            parse(fileName);
+            unreachable();
+        }
+        catch (TapestryException ex)
+        {
+            if (!ex.getMessage().contains(errorMessageSubstring))
+            {
+                throw new AssertionError(format("Message [%s] does not contain substring [%s].", ex
+                        .getMessage(), errorMessageSubstring));
+            }
+
+            assertEquals(ex.getLocation().getLine(), expectedLine);
+        }
     }
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/BlockImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/BlockImplTest.java?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/BlockImplTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/BlockImplTest.java Thu Jan 11 17:57:38 2007
@@ -0,0 +1,61 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.internal.structure;
+
+import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.runtime.RenderQueue;
+import org.testng.annotations.Test;
+
+public class BlockImplTest extends InternalBaseTestCase
+{
+    @Test
+    public void empty_block()
+    {
+        BlockImpl block = new BlockImpl(null);
+        RenderQueue queue = newRenderQueue();
+        MarkupWriter writer = newMarkupWriter();
+
+        replay();
+
+        block.render(writer, queue);
+
+        verify();
+    }
+
+    @Test
+    public void body_pushed_to_queue_backwards()
+    {
+        BlockImpl block = new BlockImpl(null);
+        RenderQueue queue = newRenderQueue();
+        MarkupWriter writer = newMarkupWriter();
+        PageElement element1 = newPageElement();
+        PageElement element2 = newPageElement();
+
+        getMocksControl().checkOrder(true);
+
+        queue.push(element2);
+        queue.push(element1);
+
+        replay();
+
+        block.addToBody(element1);
+        block.addToBody(element2);
+
+        block.render(writer, queue);
+
+        verify();
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ParameterConflict.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ParameterConflict.html?view=diff&rev=495465&r1=495464&r2=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ParameterConflict.html (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ParameterConflict.html Thu Jan 11 17:57:38 2007
@@ -1,4 +1,3 @@
-
 <t:comp type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
     <p> This component demonstrates that template values are overriden by
         bindings inside the @Component annotation, in the component class.</p>

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/block_element.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/block_element.html?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/block_element.html (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/block_element.html Thu Jan 11 17:57:38 2007
@@ -0,0 +1,10 @@
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    
+    <t:block id="block0">
+        <!-- block0 content -->
+    </t:block>
+    
+    <t:block>
+        <!-- anon block content -->
+    </t:block>
+</html>

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_blank.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_blank.html?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_blank.html (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_blank.html Thu Jan 11 17:57:38 2007
@@ -0,0 +1,7 @@
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    
+    <t:comp id="foo">
+        <t:parameter name=""/>
+    </t:comp>
+    
+</html>

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_omitted.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_omitted.html?view=auto&rev=495465
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_omitted.html (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/internal/services/name_attribute_of_parameter_element_omitted.html Thu Jan 11 17:57:38 2007
@@ -0,0 +1,7 @@
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    
+    <t:comp id="foo">
+        <t:parameter/>
+    </t:comp>
+    
+</html>