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 21:51:51 UTC

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

Author: hlship
Date: Fri Jan 12 12:51:50 2007
New Revision: 495735

URL: http://svn.apache.org/viewvc?view=rev&rev=495735
Log:
Add support for the <t:block> element in component templates, and the ability to access and inject Block instances into component classes.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/BlockNotFoundException.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectBlockWorker.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/BlockDemo.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/BlockDemo.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectBlockWorkerTest.java
Removed:
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/components/NeverRender.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAssetWorker.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.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/ComponentPageElement.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/InternalComponentResourcesImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/RenderComponentDemo.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectAssetWorkerTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/BlockNotFoundException.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/BlockNotFoundException.java?view=auto&rev=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/BlockNotFoundException.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/BlockNotFoundException.java Fri Jan 12 12:51:50 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;
+
+/**
+ * Exception thrown when a a {@link Block} is requested but not found.
+ */
+public class BlockNotFoundException extends RuntimeException
+{
+    public BlockNotFoundException(String message)
+    {
+        super(message);
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ComponentResourcesCommon.java Fri Jan 12 12:51:50 2007
@@ -109,4 +109,15 @@
      * @return the element name
      */
     String getElementName();
+
+    /**
+     * Returns a block from the component's template, referenced by its id.
+     * 
+     * @param blockId
+     *            the id of the block
+     * @return the identified Block
+     * @throws BlockNotFoundException
+     *             if no block with the given id exists
+     */
+    Block getBlock(String blockId);
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Inject.java Fri Jan 12 12:51:50 2007
@@ -21,8 +21,11 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+import org.apache.commons.logging.Log;
 import org.apache.tapestry.Asset;
+import org.apache.tapestry.Block;
 import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.ioc.Messages;
 
 /**
  * Allows injection of various objects into a component class. In certain cases, the type of the
@@ -37,9 +40,21 @@
  * </pre>
  * 
  * <p>
- * Finally, for certain specific types (such as {@link ComponentResources}), the value is omitted
- * entirely, and an object appropriate to the component instance is injected.
- * 
+ * There is a complex relationship between the type of the field and the interpretation of the
+ * annotation and its value.
+ * <p>
+ * For type {@link Asset}, the value is a relative path to the asset.
+ * <p>
+ * For type {@link Block}, the value is the id of a block in the component's template. When the
+ * value is omitted, it is deduced from the field name.
+ * <p>
+ * Finally, for certain specific types, the value is omitted entirely, and an object appropriate to
+ * the component instance is injected. This includes {@link ComponentResources}, Locale (for the
+ * containing page's locale), {@link Messages} (the component's message catalog), {@link Log} to log
+ * errors or debugging data, and String for the component's complete id.
+ * <p>
+ * And if nothing else matches and the value is omitted, then a search for Tapestry IOC service
+ * implementing the interface defined by the fields type occurs.
  * @see org.apache.tapestry.services.InjectionProvider
  */
 @Target(FIELD)

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAssetWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAssetWorker.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAssetWorker.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectAssetWorker.java Fri Jan 12 12:51:50 2007
@@ -28,7 +28,8 @@
 
 /**
  * Supports injection for fields of type {@link Asset}. This worker must be scheduled
- * <em>before</em> {@link InjectNamedWorker}.
+ * <em>before</em> {@link InjectNamedWorker}, because it uses the same annotation, {@link Inject},
+ * but interprets the value as the relative path to the asset.
  * 
  * @see AssetSource
  */
@@ -61,7 +62,7 @@
 
             // If the field has no annotation, or no value for its annotation, that's probably
             // a programmer error, but we'll let the later Inject-related workers complain about it.
-            
+
             if (annotation == null)
                 continue;
 
@@ -98,7 +99,7 @@
             transformation.makeReadOnly(name);
 
             // Keep InjectNamedWorker from doing anything to it.
-            
+
             transformation.claimField(name, annotation);
         }
 

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectBlockWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectBlockWorker.java?view=auto&rev=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectBlockWorker.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InjectBlockWorker.java Fri Jan 12 12:51:50 2007
@@ -0,0 +1,92 @@
+// 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 java.util.List;
+
+import org.apache.tapestry.Block;
+import org.apache.tapestry.annotations.Inject;
+import org.apache.tapestry.ioc.internal.util.InternalUtils;
+import org.apache.tapestry.ioc.util.BodyBuilder;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.ComponentClassTransformWorker;
+import org.apache.tapestry.services.TransformConstants;
+
+/**
+ * Identifies fields of type {@link Block} that have the {@link Inject} annotation and converts them
+ * into read-only fields containing the injected Block from the template. The annotation's value is
+ * the id of the block to inject; if ommitted, the block id is deduced from the field id.
+ * <p>
+ * Must be scheduled before {@link InjectNamedWorker} because it uses the same annotation, Inject,
+ * with a different interpretation.
+ */
+public class InjectBlockWorker implements ComponentClassTransformWorker
+{
+    static final String BLOCK_TYPE_NAME = Block.class.getName();
+
+    public void transform(ClassTransformation transformation, MutableComponentModel model)
+    {
+        List<String> fieldNames = transformation.findFieldsOfType(BLOCK_TYPE_NAME);
+
+        if (fieldNames.isEmpty())
+            return;
+
+        BodyBuilder builder = new BodyBuilder();
+        builder.begin();
+
+        int count = 0;
+
+        String resourcesFieldName = transformation.getResourcesFieldName();
+
+        for (String fieldName : fieldNames)
+        {
+            Inject annotation = transformation.getFieldAnnotation(fieldName, Inject.class);
+
+            if (annotation == null)
+                continue;
+
+            String blockId = getBlockId(fieldName, annotation);
+
+            builder.addln("%s = %s.getBlock(\"%s\");", fieldName, resourcesFieldName, blockId);
+
+            transformation.makeReadOnly(fieldName);
+            transformation.claimField(fieldName, annotation);
+
+            count++;
+        }
+
+        // Fields yes, but no annotations, so nothing to really do.
+
+        if (count == 0)
+            return;
+
+        builder.end();
+
+        transformation.extendMethod(TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE, builder
+                .toString());
+    }
+
+    private String getBlockId(String fieldName, Inject annotation)
+    {
+        String annotationId = annotation.value();
+
+        if (InternalUtils.isNonBlank(annotationId))
+            return annotationId;
+
+        return InternalUtils.stripMemberPrefix(fieldName);
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/InternalClassTransformationImpl.java Fri Jan 12 12:51:50 2007
@@ -48,6 +48,7 @@
 import org.apache.tapestry.internal.InternalComponentResources;
 import org.apache.tapestry.internal.util.MultiKey;
 import org.apache.tapestry.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry.ioc.internal.util.Defense;
 import org.apache.tapestry.ioc.internal.util.IdAllocator;
 import org.apache.tapestry.ioc.internal.util.InternalUtils;
 import org.apache.tapestry.model.ComponentModel;
@@ -196,6 +197,7 @@
 
         _constructor.append(");\n");
 
+        // The "}" will be added later, inside 
     }
 
     private void freeze()
@@ -1166,6 +1168,8 @@
         _constructor.append("  ");
         _constructor.append(initializer);
 
+        // This finally matches the "{" added inside the constructor
+        
         _constructor.append("();\n\n}");
 
         String constructorBody = _constructor.toString();
@@ -1469,4 +1473,14 @@
     {
         return _log;
     }
+
+    public void addToConstructor(String statement)
+    {
+        Defense.notNull(statement, "statement");
+
+        failIfFrozen();
+
+        _constructor.append(statement);
+    }
+
 }

Modified: 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=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageLoaderProcessor.java Fri Jan 12 12:51:50 2007
@@ -27,6 +27,7 @@
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.internal.InternalConstants;
 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.CommentToken;
 import org.apache.tapestry.internal.parser.ComponentTemplate;
@@ -36,6 +37,7 @@
 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.BlockImpl;
 import org.apache.tapestry.internal.structure.BodyPageElement;
 import org.apache.tapestry.internal.structure.ComponentPageElement;
 import org.apache.tapestry.internal.structure.Page;
@@ -75,7 +77,7 @@
 
     private final LinkedList<Boolean> _discardEndTagStack = newLinkedList();
 
-    private final LinkedList<Runnable> _endElementStack = newLinkedList();
+    private final LinkedList<Runnable> _endElementCommandStack = newLinkedList();
 
     private final IdAllocator _idAllocator = new IdAllocator();
 
@@ -233,7 +235,7 @@
             addToBody(element);
         }
 
-        Runnable runnable = _endElementStack.removeFirst();
+        Runnable runnable = _endElementCommandStack.removeFirst();
 
         // Used to return environment to prior state.
 
@@ -397,6 +399,10 @@
                     comment((CommentToken) token);
                     break;
 
+                case BLOCK:
+                    block((BlockToken) token);
+                    break;
+
                 default:
                     throw new IllegalStateException("Not implemented yet: " + token);
             }
@@ -410,6 +416,33 @@
         // as the ComponentTemplate is valid.
     }
 
+    private void block(BlockToken token)
+    {
+        // Don't use the page element factory here becauses we need something that is both Block and
+        // BodyPageElement
+        // and don't want to use casts.
+
+        BlockImpl block = new BlockImpl(token.getLocation());
+
+        String id = token.getId();
+
+        if (id != null)
+            _loadingElement.addBlock(id, block);
+
+        _bodyPageElementStack.addFirst(block);
+        _discardEndTagStack.addFirst(true);
+
+        Runnable cleanup = new Runnable()
+        {
+            public void run()
+            {
+                _bodyPageElementStack.removeFirst();
+            }
+        };
+
+        _endElementCommandStack.add(cleanup);
+    }
+
     private void startComponent(StartComponentToken token)
     {
         String elementName = token.getElementName();
@@ -503,7 +536,7 @@
             }
         };
 
-        _endElementStack.addFirst(cleanup);
+        _endElementCommandStack.addFirst(cleanup);
     }
 
     private void startElement(StartElementToken token)
@@ -520,7 +553,7 @@
         // Do NOT discard the end tag; add it to the body.
 
         _discardEndTagStack.addFirst(false);
-        _endElementStack.addFirst(NO_OP);
+        _endElementCommandStack.addFirst(NO_OP);
     }
 
     private void text(TextToken token)

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=495735&r1=495734&r2=495735
==============================================================================
--- 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 Fri Jan 12 12:51:50 2007
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry.internal.structure;
 
+import org.apache.tapestry.Block;
 import org.apache.tapestry.ComponentResourcesCommon;
 import org.apache.tapestry.internal.InternalComponentResources;
 import org.apache.tapestry.internal.InternalComponentResourcesCommon;
@@ -53,8 +54,14 @@
      * ... where a template contains elements that are all components, those components will receive
      * portions of the template as their body.
      */
-
     void addToTemplate(PageElement element);
+
+    /**
+     * Used during the contruction of a page to add a non-annonymous Block to the component.
+     * 
+     * @see ComponentResourcesCommon#getBlock(String)
+     */
+    void addBlock(String blockId, Block block);
 
     /**
      * Adds a component to its container. The embedded component's id must be unique within the

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java Fri Jan 12 12:51:50 2007
@@ -24,6 +24,8 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.tapestry.Binding;
+import org.apache.tapestry.Block;
+import org.apache.tapestry.BlockNotFoundException;
 import org.apache.tapestry.ComponentEventHandler;
 import org.apache.tapestry.Link;
 import org.apache.tapestry.MarkupWriter;
@@ -358,6 +360,8 @@
         }
     };
 
+    private Map<String, Block> _blocks;
+
     private List<PageElement> _body;
 
     private Map<String, ComponentPageElement> _children;
@@ -474,9 +478,10 @@
         }
     };
 
-    // We know that, at the very least, there will be an element to force the component to render its body,
+    // We know that, at the very least, there will be an element to force the component to render
+    // its body,
     // so there's no reason to wait to initialize the list.
-    
+
     private final List<PageElement> _template = newList();
 
     private final TypeCoercer _typeCoercer;
@@ -939,7 +944,7 @@
     {
         // TODO: An error if the _render flag is already set (recursive rendering not
         // allowed or advisable).
-        
+
         // Once we start rendering, the page is considered dirty, until we cleanup post render.
 
         _page.incrementDirtyCount();
@@ -1008,6 +1013,26 @@
     public void queueRender(RenderQueue queue)
     {
         queue.push(this);
+    }
+
+    public Block getBlock(String id)
+    {
+        Block result = InternalUtils.get(_blocks, id);
+
+        if (result == null)
+            throw new BlockNotFoundException(StructureMessages.blockNotFound(_completeId, id));
+
+        return result;
+    }
+
+    public void addBlock(String blockId, Block block)
+    {
+        if (_blocks == null)
+            _blocks = newMap();
+
+        // TODO: Check for duplicates.
+
+        _blocks.put(blockId, block);
     }
 
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/InternalComponentResourcesImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/InternalComponentResourcesImpl.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/InternalComponentResourcesImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/InternalComponentResourcesImpl.java Fri Jan 12 12:51:50 2007
@@ -21,6 +21,7 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.tapestry.Binding;
+import org.apache.tapestry.Block;
 import org.apache.tapestry.ComponentEventHandler;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.Link;
@@ -293,4 +294,8 @@
         _element.queueRender(queue);
     }
 
+    public Block getBlock(String blockId)
+    {
+        return _element.getBlock(blockId);
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/StructureMessages.java Fri Jan 12 12:51:50 2007
@@ -70,4 +70,9 @@
     {
         return MESSAGES.format("wrong-event-result-type", method, expectedType.getName());
     }
+
+    static String blockNotFound(String componentId, String blockId)
+    {
+        return MESSAGES.format("block-not-found", componentId, blockId);
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/ClassTransformation.java Fri Jan 12 12:51:50 2007
@@ -267,6 +267,14 @@
     void addMethod(MethodSignature signature, String methodBody);
 
     /**
+     * Adds a statement to the constructor.
+     * 
+     * @param statement
+     *            the statement to add, which should end with a semicolon
+     */
+    void addToConstructor(String statement);
+
+    /**
      * Replaces all read-references to the specified field with invocations of the specified method
      * name. Replacements do not occur in methods added via
      * {@link #addMethod(MethodSignature, String)} or {@link #extendMethod(MethodSignature, String)}.

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TapestryModule.java Fri Jan 12 12:51:50 2007
@@ -78,6 +78,7 @@
 import org.apache.tapestry.internal.services.InfrastructureManagerImpl;
 import org.apache.tapestry.internal.services.InjectAnonymousWorker;
 import org.apache.tapestry.internal.services.InjectAssetWorker;
+import org.apache.tapestry.internal.services.InjectBlockWorker;
 import org.apache.tapestry.internal.services.InjectComponentWorker;
 import org.apache.tapestry.internal.services.InjectNamedWorker;
 import org.apache.tapestry.internal.services.InjectPageWorker;
@@ -629,6 +630,7 @@
      * <li>InjectAnnonymous -- used with the {@link Inject} annotation, when no value is supplied</li>
      * <li>InjectPage -- adds code to allow access to other pages via the {@link InjectPage} field
      * annotation</li>
+     * <li>InjectBlock -- allows a block from the template to be injected into a field</li>
      * <li>SupportsInformalParameters -- checks for the annotation</li>
      * <li>UnclaimedField -- identifies unclaimed fields and resets them to null/0/false at the end
      * of the request</li>
@@ -657,12 +659,15 @@
                 new InjectAnonymousWorker(locator, injectionProvider),
                 "after:InjectNamed");
         configuration.add("InjectAsset", new InjectAssetWorker(assetSource), "before:InjectNamed");
+        configuration.add("InjectBlock", new InjectBlockWorker(), "before:InjectNamed");
+
         configuration.add("MixinAfter", new MixinAfterWorker());
         configuration.add("Component", new ComponentWorker(resolver));
         configuration.add("Environment", new EnvironmentalWorker(environment));
         configuration.add("Mixin", new MixinWorker(resolver));
         configuration.add("OnEvent", new OnEventWorker());
-        configuration.add("SupportsInformalParameters", new SupportsInformalParametersWorker());
+        configuration.add("SupportsInf" +
+                "ormalParameters", new SupportsInformalParametersWorker());
         configuration.add("InjectPage", new InjectPageWorker(requestPageCache));
         configuration.add("InjectComponent", new InjectComponentWorker());
         configuration.add("RenderCommand", new RenderCommandWorker());

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/TapestryTestCase.java Fri Jan 12 12:51:50 2007
@@ -34,6 +34,7 @@
 
 import org.apache.tapestry.Asset;
 import org.apache.tapestry.Binding;
+import org.apache.tapestry.Block;
 import org.apache.tapestry.ComponentEventHandler;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.ComponentResourcesCommon;
@@ -643,5 +644,25 @@
     protected final Link newLink()
     {
         return newMock(Link.class);
+    }
+
+    protected final Block newBlock()
+    {
+        return newMock(Block.class);
+    }
+
+    protected final void train_getSupportsInformalParameters(ComponentModel model, boolean supports)
+    {
+        expect(model.getSupportsInformalParameters()).andReturn(supports);
+    }
+
+    protected final Inject newInject()
+    {
+        return newMock(Inject.class);
+    }
+
+    protected final void train_findFieldsOfType(ClassTransformation transformation, String type, String... fieldNames)
+    {
+        expect(transformation.findFieldsOfType(type)).andReturn(Arrays.asList(fieldNames));
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/structure/StructureStrings.properties Fri Jan 12 12:51:50 2007
@@ -22,4 +22,5 @@
 wrong-event-result-type=Return value of method %s is not compatible with the expected return type of %s. \
   The value has been ignored. \
   Further lifecycle methods may be invoked, which is likely to cause incorrect application behavior. \
-  You should change the method to return the correct type. 
\ No newline at end of file
+  You should change the method to return the correct type. 
+block-not-found=Template for component %s does not contain a block with identifier '%s'.
\ No newline at end of file

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/BlockDemo.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/BlockDemo.html?view=auto&rev=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/BlockDemo.html (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/BlockDemo.html Fri Jan 12 12:51:50 2007
@@ -0,0 +1,19 @@
+<html t:type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    
+    <h1>Block Demo</h1>
+    
+    <p>
+        This page demonstrates how blocks may be used to contain text and other elements and control when and
+        if they are rendered.
+</p>    
+    
+    <form t:type="Form">
+        <select t:type="Select" t:id="blockName" model="',fred,barney'" onchange="'this.form.submit();'"/> <label for="blockName">Block to display</label>
+    </form>
+    
+    <p>The block: [<t:comp type="Render" value="blockToRender"/>]</p>
+    
+<t:block id="fred">Block fred.</t:block>
+<t:block id="barney">Block barney.</t:block>
+    
+</html>

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/RenderComponentDemo.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/RenderComponentDemo.html?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/RenderComponentDemo.html (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/app1/WEB-INF/RenderComponentDemo.html Fri Jan 12 12:51:50 2007
@@ -6,11 +6,11 @@
         This page demonstrates how a component may return a component instance from one of its render phase methods to allow that object to render.
     </p>
 
-    <t:comp type="NeverRender">
+    <t:block>
         <span t:id="optional">
             Optional Text
         </span>
-    </t:comp>
+    </t:block>
     
     <form t:type="Form">
         <input t:type="Checkbox" t:id="enabled" onchange="'this.form.submit();'"/> <label for="enabled">Enable optional text</label>

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/app1/index.html Fri Jan 12 12:51:50 2007
@@ -82,6 +82,9 @@
                         <li>
                             <a href="RenderComponentDemo.html">RenderComponentDemo</a> -- components that "nominate" other components to render
                         </li>
+                        <li>
+                            <a href="BlockDemo.html">BlockDemo</a> -- use of blocks to control rendering
+                        </li>
                     </ul>
                 </td>
             </tr>

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/IntegrationTests.java Fri Jan 12 12:51:50 2007
@@ -109,7 +109,7 @@
         // This is text passed from Start.html to Output as a parameter
 
         assertTextPresent("we have basic parameters working");
-        
+
         assertSourcePresent("<!-- my comment -->");
     }
 
@@ -494,6 +494,29 @@
         // after all.
 
         assertText("//span[@id='optional']", "Optional Text");
+    }
+
+    /**
+     * Tests the ability to inject a Block, and the ability to use the block to control rendering.
+     */
+    @Test
+    public void block_rendering() throws Exception
+    {
+        _selenium.open(BASE_URL);
+        clickAndWait("link=BlockDemo");
+
+        assertTextPresent("[]");
+
+        _selenium.select("//select[@id='blockName']", "fred");
+        _selenium.waitForPageToLoad(PAGE_LOAD_TIMEOUT);
+
+        assertTextPresent("[Block fred.]");
+
+        _selenium.select("//select[@id='blockName']", "barney");
+        _selenium.waitForPageToLoad(PAGE_LOAD_TIMEOUT);
+
+        assertTextPresent("[Block barney.]");
+
     }
 
     private byte[] readContent(URL url) throws Exception

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/BlockDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/BlockDemo.java?view=auto&rev=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/BlockDemo.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/BlockDemo.java Fri Jan 12 12:51:50 2007
@@ -0,0 +1,64 @@
+// 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.integration.app1.pages;
+
+import java.util.Map;
+
+import org.apache.tapestry.Block;
+import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.Inject;
+import org.apache.tapestry.annotations.Persist;
+import org.apache.tapestry.annotations.Retain;
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
+
+@ComponentClass
+public class BlockDemo
+{
+    @Inject
+    private Block _fred;
+
+    @Inject
+    private Block _barney;
+
+    // Blocks not injected until page load, so must lazily initialize the map.
+    @Retain
+    private Map<String, Block> _blocks = null;
+
+    @Persist
+    private String _blockName;
+
+    public Block getBlockToRender()
+    {
+        if (_blocks == null)
+        {
+            _blocks = CollectionFactory.newMap();
+            _blocks.put("fred", _fred);
+            _blocks.put("barney", _barney);
+        }
+
+        return _blocks.get(_blockName);
+    }
+
+    public String getBlockName()
+    {
+        return _blockName;
+    }
+
+    public void setBlockName(String blockName)
+    {
+        _blockName = blockName;
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectAssetWorkerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectAssetWorkerTest.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectAssetWorkerTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectAssetWorkerTest.java Fri Jan 12 12:51:50 2007
@@ -14,8 +14,6 @@
 
 package org.apache.tapestry.internal.services;
 
-import java.util.Arrays;
-
 import org.apache.tapestry.annotations.Inject;
 import org.apache.tapestry.internal.test.InternalBaseTestCase;
 import org.apache.tapestry.ioc.Resource;
@@ -101,11 +99,5 @@
         new InjectAssetWorker(source).transform(ct, model);
 
         verify();
-    }
-
-    protected final void train_findFieldsOfType(ClassTransformation transformation,
-            String typeName, String... names)
-    {
-        expect(transformation.findFieldsOfType(typeName)).andReturn(Arrays.asList(names));
     }
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectBlockWorkerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectBlockWorkerTest.java?view=auto&rev=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectBlockWorkerTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InjectBlockWorkerTest.java Fri Jan 12 12:51:50 2007
@@ -0,0 +1,107 @@
+// 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 org.apache.tapestry.annotations.Inject;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.TransformConstants;
+import org.apache.tapestry.test.TapestryTestCase;
+import org.testng.annotations.Test;
+
+/**
+ * Tests a couple of edge cases where there's nothing to inject.
+ */
+public class InjectBlockWorkerTest extends TapestryTestCase
+{
+    @Test
+    public void no_fields_of_type_block()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+
+        train_findFieldsOfType(ct, InjectBlockWorker.BLOCK_TYPE_NAME);
+
+        replay();
+
+        new InjectBlockWorker().transform(ct, model);
+
+        verify();
+    }
+
+    @Test
+    public void field_missing_annotation()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+
+        train_findFieldsOfType(ct, InjectBlockWorker.BLOCK_TYPE_NAME, "fred");
+
+        train_getResourcesFieldName(ct, "rez");
+
+        train_getFieldAnnotation(ct, "fred", Inject.class, null);
+
+        replay();
+
+        new InjectBlockWorker().transform(ct, model);
+
+        verify();
+    }
+
+    /**
+     * This doesn't prove anything; later there will be integration tests that prove that the
+     * generated code is valid and works.
+     */
+    @Test
+    public void fields_with_annotations()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        Inject fredAnnotation = newInject();
+        Inject barneyAnnotation = newInject();
+
+        train_findFieldsOfType(ct, InjectBlockWorker.BLOCK_TYPE_NAME, "fred", "_barneyBlock");
+
+        train_getResourcesFieldName(ct, "rez");
+
+        train_getFieldAnnotation(ct, "fred", Inject.class, fredAnnotation);
+
+        train_value(fredAnnotation, "");
+
+        ct.makeReadOnly("fred");
+        ct.claimField("fred", fredAnnotation);
+
+        train_getFieldAnnotation(ct, "_barneyBlock", Inject.class, barneyAnnotation);
+        train_value(barneyAnnotation, "barney");
+
+        ct.makeReadOnly("_barneyBlock");
+        ct.claimField("_barneyBlock", barneyAnnotation);
+
+        train_extendMethod(
+                ct,
+                TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE,
+                "{",
+                "fred = rez.getBlock(\"fred\");",
+                "_barneyBlock = rez.getBlock(\"barney\");",
+                "}");
+
+        replay();
+
+        new InjectBlockWorker().transform(ct, model);
+
+        verify();
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/InternalClassTransformationImplTest.java Fri Jan 12 12:51:50 2007
@@ -675,6 +675,35 @@
     }
 
     @Test
+    public void add_to_constructor() throws Exception
+    {
+        InternalComponentResources resources = newInternalComponentResources();
+
+        Log log = newLog();
+
+        replay();
+
+        CtClass targetObjectCtClass = findCtClass(ReadOnlyBean.class);
+
+        InternalClassTransformation ct = new InternalClassTransformationImpl(targetObjectCtClass,
+                _contextClassLoader, log, null);
+
+        ct.addToConstructor("_value = \"from constructor\";");
+
+        ct.finish();
+
+        Class transformed = _classPool.toClass(targetObjectCtClass, _loader);
+
+        Object target = ct.createInstantiator(transformed).newInstance(resources);
+
+        PropertyAccess access = new PropertyAccessImpl();
+
+        assertEquals(access.get(target, "value"), "from constructor");
+
+        verify();
+    }
+
+    @Test
     public void inject_field() throws Exception
     {
         InternalComponentResources resources = newInternalComponentResources();

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java?view=diff&rev=495735&r1=495734&r2=495735
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/structure/ComponentPageElementImplTest.java Fri Jan 12 12:51:50 2007
@@ -15,6 +15,8 @@
 package org.apache.tapestry.internal.structure;
 
 import org.apache.tapestry.Binding;
+import org.apache.tapestry.Block;
+import org.apache.tapestry.BlockNotFoundException;
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.internal.InternalComponentResources;
 import org.apache.tapestry.internal.services.Instantiator;
@@ -31,6 +33,62 @@
 public class ComponentPageElementImplTest extends InternalBaseTestCase
 {
     @Test
+    public void block_not_found()
+    {
+        Page page = newPage();
+        Component component = newComponent();
+        ComponentModel model = newComponentModel();
+        TypeCoercer coercer = newTypeCoercer();
+
+        Instantiator ins = newInstantiator(component, model);
+
+        replay();
+
+        ComponentPageElement cpe = new ComponentPageElementImpl(page, ins, coercer, null);
+
+        ComponentResources resources = cpe.getComponentResources();
+
+        try
+        {
+            resources.getBlock("notFound");
+            unreachable();
+        }
+        catch (BlockNotFoundException ex)
+        {
+            assertTrue(ex
+                    .getMessage()
+                    .matches(
+                            "Template for component .* does not contain a block with identifier 'notFound'\\."));
+        }
+
+        verify();
+    }
+
+    @Test
+    public void block_found()
+    {
+        Page page = newPage();
+        Component component = newComponent();
+        ComponentModel model = newComponentModel();
+        TypeCoercer coercer = newTypeCoercer();
+        Block block = newBlock();
+
+        Instantiator ins = newInstantiator(component, model);
+
+        replay();
+
+        ComponentPageElement cpe = new ComponentPageElementImpl(page, ins, coercer, null);
+
+        ComponentResources resources = cpe.getComponentResources();
+
+        cpe.addBlock("known", block);
+
+        assertSame(resources.getBlock("known"), block);
+
+        verify();
+    }
+
+    @Test
     public void parameter_is_bound()
     {
         Page page = newPage();
@@ -226,11 +284,6 @@
         assertSame(cpe.getComponentResources().readParameter("barney", Long.class), boundValue);
 
         verify();
-    }
-
-    protected final void train_getSupportsInformalParameters(ComponentModel model, boolean supports)
-    {
-        expect(model.getSupportsInformalParameters()).andReturn(supports);
     }
 
     @Test