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

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

Author: hlship
Date: Fri Oct 13 09:44:20 2006
New Revision: 463734

URL: http://svn.apache.org/viewvc?view=rev&rev=463734
Log:
Continue adding support for component events.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventDispatcher.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageRenderDispatcher.java
      - copied, changed from r462686, tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/HTMLDispatcher.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/pages/EventHandlerTarget.java
Removed:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/HTMLDispatcher.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/OnEvent.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.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/ServicesMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElement.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/ComponentPageElementImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/Page.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentLifecycle.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/services/TransformConstants.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TransformUtils.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/BaseTestCase.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ActionPage.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentEventImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.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/services/TypeCoercerImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/services/TransformUtilsTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ActionPage.html

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/OnEvent.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/OnEvent.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/OnEvent.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/OnEvent.java Fri Oct 13 09:44:20 2006
@@ -46,7 +46,7 @@
      * @return
      * @see TapestryConstants
      */
-    String[] value() default "";
+    String[] value() default {};
 
     /**
      * A list of component ids. The handler will only be invoked if the id of the event originating
@@ -54,6 +54,6 @@
      * 
      * @return
      */
-    String[] component() default "";
+    String[] component() default {};
 
 }

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventDispatcher.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventDispatcher.java?view=auto&rev=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventDispatcher.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventDispatcher.java Fri Oct 13 09:44:20 2006
@@ -0,0 +1,81 @@
+package org.apache.tapestry.internal.services;
+
+import java.io.IOException;
+
+import org.apache.tapestry.ComponentEventHandler;
+import org.apache.tapestry.internal.structure.ComponentPageElement;
+import org.apache.tapestry.internal.structure.Page;
+import org.apache.tapestry.services.Dispatcher;
+import org.apache.tapestry.services.WebRequest;
+import org.apache.tapestry.services.WebResponse;
+
+/**
+ * Processes component action events sent as requests from the client. Action events include an
+ * event type, identify a page and a component, and may provide additional context strings.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public class ComponentEventDispatcher implements Dispatcher
+{
+    private final PageResponseRenderer _renderer;
+
+    private final RequestPageCache _cache;
+
+    public ComponentEventDispatcher(final PageResponseRenderer renderer, final RequestPageCache cache)
+    {
+        _renderer = renderer;
+        _cache = cache;
+    }
+
+    public boolean dispatch(WebRequest request, WebResponse response) throws IOException
+    {
+        String path = request.getPath();
+
+        int dotx = path.indexOf('.');
+
+        if (dotx < 0)
+            return false;
+
+        // Skip the leading slash, the rest is logical page name.
+
+        String logicalPageName = path.substring(1, dotx);
+
+        Page page = _cache.get(logicalPageName);
+
+        int slashx = path.indexOf('/', dotx + 1);
+
+        String eventType = path.substring(dotx + 1, slashx);
+
+        String remainder = path.substring(slashx + 1);
+
+        String[] chunks = remainder.split("/");
+
+        String nestedComponentId = chunks[0];
+
+        String[] context = new String[chunks.length - 1];
+        for (int i = 1; i < chunks.length; i++)
+            context[i - 1] = chunks[i];
+
+        ComponentPageElement element = page.getComponentByNestedId(nestedComponentId);
+
+        ComponentEventHandler handler = new ComponentEventHandler()
+        {
+            public void handleResult(Object result, String methodDescription)
+            {
+                // Does nothing, right now. Accepts any return value.
+            }
+        };
+
+        element.triggerEvent(eventType, context, handler);
+
+        // TODO: maybe the return value from triggerEvent should be true if any handler was found,
+        // false if no handler was found. The latter feels like an error condition.
+
+        // Once the pieces fall into place, we won't just blindly re-render the page.
+
+        _renderer.renderPageResponse(page, response);
+
+        return true;
+    }
+
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.java Fri Oct 13 09:44:20 2006
@@ -68,8 +68,14 @@
 
     public boolean storeResult(Object result, String methodDescription)
     {
+        // Given that this method is *only* invoked from code
+        // that is generated at runtime and proven to be correct,
+        // this should never, ever happen. But what the hell,
+        // let's check anyway.
+
         if (_aborted)
-            throw new IllegalStateException();
+            throw new IllegalStateException(ServicesMessages
+                    .componentEventIsAborted(methodDescription));
 
         if (result == null)
             return false;
@@ -81,7 +87,8 @@
         return true;
     }
 
-    public <T> T coerceContext(int index, Class<T> desiredType, String methodDescription)
+    @SuppressWarnings("unchecked")
+    public Object coerceContext(int index, String desiredTypeName, String methodDescription)
     {
         if (index >= _context.length)
             throw new IllegalArgumentException(ServicesMessages
@@ -89,6 +96,8 @@
 
         try
         {
+            Class desiredType = Class.forName(desiredTypeName);
+
             return _typeCoercer.coerce(_context[index], desiredType);
         }
         catch (Exception ex)

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=463734&r1=463733&r2=463734
==============================================================================
--- 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 Oct 13 09:44:20 2006
@@ -269,6 +269,23 @@
         return findAnnotationInList(annotationClass, annotations);
     }
 
+    public <T extends Annotation> T getMethodAnnotation(MethodSignature signature,
+            Class<T> annotationClass)
+    {
+        failIfFrozen();
+
+        CtMethod method = findMethod(signature);
+
+        if (method == null)
+            throw new IllegalArgumentException(ServicesMessages.noDeclaredMethod(
+                    _ctClass,
+                    signature));
+
+        List<Annotation> annotations = findMethodAnnotations(method);
+
+        return findAnnotationInList(annotationClass, annotations);
+    }
+
     /**
      * Searches an array of objects (that are really annotations instances) to find one that is of
      * the correct type, which is returned.
@@ -581,7 +598,10 @@
         }
         catch (CannotCompileException ex)
         {
-            throw new RuntimeException(ex);
+            throw new MethodCompileException(ServicesMessages.methodCompileError(
+                    methodSignature,
+                    methodBody,
+                    ex), methodBody, ex);
         }
 
         addMethodToDescription("extend", methodSignature, methodBody);
@@ -622,11 +642,10 @@
 
     private CtMethod findMethod(MethodSignature methodSignature)
     {
-        for (CtMethod method : _ctClass.getDeclaredMethods())
-        {
-            if (match(method, methodSignature))
-                return method;
-        }
+        CtMethod method = findDeclaredMethod(methodSignature);
+
+        if (method != null)
+            return method;
 
         CtMethod result = addOverrideOfSuperclassMethod(methodSignature);
 
@@ -634,8 +653,19 @@
             return result;
 
         throw new IllegalArgumentException(ServicesMessages.noDeclaredMethod(
-                methodSignature,
-                _ctClass));
+                _ctClass,
+                methodSignature));
+    }
+
+    private CtMethod findDeclaredMethod(MethodSignature methodSignature)
+    {
+        for (CtMethod method : _ctClass.getDeclaredMethods())
+        {
+            if (match(method, methodSignature))
+                return method;
+        }
+
+        return null;
     }
 
     private CtMethod addOverrideOfSuperclassMethod(MethodSignature methodSignature)
@@ -932,7 +962,10 @@
             throw new RuntimeException(ex);
         }
 
-        String fieldName = addField(Modifier.PROTECTED, type.getName(), suggestedName);
+        String fieldName = addField(
+                Modifier.PROTECTED | Modifier.FINAL,
+                type.getName(),
+                suggestedName);
 
         addInjectToConstructor(fieldName, ctType, value);
 

Added: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java?view=auto&rev=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java Fri Oct 13 09:44:20 2006
@@ -0,0 +1,138 @@
+package org.apache.tapestry.internal.services;
+
+import java.util.List;
+
+import org.apache.tapestry.annotations.OnEvent;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.runtime.ComponentLifecycle;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.ComponentClassTransformWorker;
+import org.apache.tapestry.services.MethodSignature;
+import org.apache.tapestry.services.TransformConstants;
+import org.apache.tapestry.services.TransformUtils;
+import org.apache.tapestry.util.BodyBuilder;
+
+/**
+ * Provides implementations of the
+ * {@link ComponentLifecycle#handleComponentEvent(org.apache.tapestry.runtime.ComponentEvent)}
+ * method, based on {@link OnEvent} annotations.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public class OnEventWorker implements ComponentClassTransformWorker
+{
+    public void transform(ClassTransformation transformation, MutableComponentModel model)
+    {
+        List<MethodSignature> methods = transformation.findMethodsWithAnnotation(OnEvent.class);
+
+        // No methods, no work.
+
+        if (methods.isEmpty())
+            return;
+
+        BodyBuilder builder = new BodyBuilder();
+        builder.begin();
+
+        builder.addln("if ($1.isAborted()) return;");
+        builder.addln("String methodName = null;");
+
+        for (MethodSignature method : methods)
+            addCodeForMethod(builder, method, transformation);
+
+        builder.end();
+
+        transformation.extendMethod(TransformConstants.HANDLE_COMPONENT_EVENT, builder.toString());
+    }
+
+    private void addCodeForMethod(BodyBuilder builder, MethodSignature method,
+            ClassTransformation transformation)
+    {
+        // $1 is the event
+
+        int closeCount = 0;
+
+        OnEvent annotation = transformation.getMethodAnnotation(method, OnEvent.class);
+
+        String[] eventTypes = annotation.value();
+
+        if (eventTypes.length > 0)
+        {
+            String fieldName = transformation.addInjectedField(
+                    String[].class,
+                    "eventTypes",
+                    eventTypes);
+
+            builder.addln("if ($1.matchesByEventType(%s))", fieldName);
+            builder.begin();
+
+            closeCount++;
+        }
+
+        String[] componentIds = annotation.component();
+
+        if (componentIds.length > 0)
+        {
+            String fieldName = transformation.addInjectedField(
+                    String[].class,
+                    "componentIds",
+                    componentIds);
+
+            builder.addln("if ($1.matchesByComponentId(%s, %s))", transformation
+                    .getResourcesFieldName(), fieldName);
+            builder.begin();
+
+            closeCount++;
+        }
+
+        // Several subsequent calls need to know the method name.
+
+        builder.addln("methodName = \"%s.%s\";", transformation.getClassName(), method
+                .getMediumDescription());
+
+        boolean isNonVoid = !method.getReturnType().equals("void");
+
+        if (isNonVoid)
+            builder.add("if ($1.storeResult(");
+
+        builder.add("%s(", method.getMethodName());
+
+        for (int i = 0; i < method.getParameterTypes().length; i++)
+        {
+            if (i > 0)
+                builder.add(", ");
+
+            addCoercedContextAsParameter(builder, i, method.getParameterTypes()[i]);
+        }
+
+        if (isNonVoid)
+            builder.addln("), methodName) return;");
+        else
+            builder.addln(");");
+
+        for (int i = 0; i < closeCount; i++)
+            builder.end();
+    }
+
+    private void addCoercedContextAsParameter(BodyBuilder builder, int index, String type)
+    {
+        boolean isPrimitive = TransformUtils.isPrimitive(type);
+        String wrapperType = TransformUtils.getWrapperTypeName(type);
+
+        // Add a cast to the wrapper type up front
+
+        if (isPrimitive)
+            builder.add("((%s)", wrapperType);
+
+        // The strings for desired type name will likely repeat a bit; it may be
+        // worth it to inject them as final fields. Could increase the number
+        // of constructor parameters pretty dramatically, however, and will reduce
+        // the readability of the output method bodies.
+
+        builder.add("$1.coerceContext(%d, \"%s\", methodName)", index, wrapperType);
+
+        // and invoke a method on the cast value to get back to primitive
+        if (isPrimitive)
+            builder.add(").%s()", TransformUtils.getUnwrapperMethodName(type));
+    }
+
+}

Copied: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageRenderDispatcher.java (from r462686, tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/HTMLDispatcher.java)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageRenderDispatcher.java?view=diff&rev=463734&p1=tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/HTMLDispatcher.java&r1=462686&p2=tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageRenderDispatcher.java&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/HTMLDispatcher.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/PageRenderDispatcher.java Fri Oct 13 09:44:20 2006
@@ -27,13 +27,13 @@
  * 
  * @author Howard M. Lewis Ship
  */
-public class HTMLDispatcher implements Dispatcher
+public class PageRenderDispatcher implements Dispatcher
 {
     private final PageResponseRenderer _renderer;
 
     private final RequestPageCache _cache;
 
-    public HTMLDispatcher(PageResponseRenderer renderer, RequestPageCache cache)
+    public PageRenderDispatcher(PageResponseRenderer renderer, RequestPageCache cache)
     {
         _renderer = renderer;
         _cache = cache;
@@ -54,9 +54,9 @@
 
         // The first character will be the leading slash
 
-        String minimalPageName = path.substring(1, pos);
+        String logicalPageName = path.substring(1, pos);
 
-        Page page = _cache.get(minimalPageName);
+        Page page = _cache.get(logicalPageName);
 
         _renderer.renderPageResponse(page, response);
 

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=463734&r1=463733&r2=463734
==============================================================================
--- 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 Fri Oct 13 09:44:20 2006
@@ -84,9 +84,9 @@
         { fieldName, ctClass.getName(), existingTag, newTag });
     }
 
-    static String noDeclaredMethod(MethodSignature methodSignature, CtClass ctClass)
+    static String noDeclaredMethod(CtClass ctClass, MethodSignature methodSignature)
     {
-        return MESSAGES.format("no-declared-method", methodSignature, ctClass.getName());
+        return MESSAGES.format("no-declared-method", ctClass.getName(), methodSignature);
     }
 
     static String incorrectClassForInstantiator(String className, Class componentClass)
@@ -211,5 +211,10 @@
     {
         return MESSAGES
                 .format("exception-in-method-parameter", methodDescription, index + 1, cause);
+    }
+
+    static String componentEventIsAborted(String methodDescription)
+    {
+        return MESSAGES.format("component-event-is-aborted", methodDescription);
     }
 }

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=463734&r1=463733&r2=463734
==============================================================================
--- 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 Oct 13 09:44:20 2006
@@ -15,6 +15,7 @@
 package org.apache.tapestry.internal.structure;
 
 import org.apache.tapestry.Binding;
+import org.apache.tapestry.ComponentEventHandler;
 import org.apache.tapestry.internal.InternalComponentResources;
 import org.apache.tapestry.runtime.RenderCommand;
 import org.apache.tapestry.runtime.RenderQueue;
@@ -64,6 +65,32 @@
      */
     void addEmbeddedElement(ComponentPageElement child);
 
+    /**
+     * 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);
+
+    /**
+     * Triggers a component event. A search for a event handling method will occur, first in the
+     * component, then its container, and so on.
+     * 
+     * @param eventType
+     *            event type (as determined from the request)
+     * @param context
+     *            the request context (as extracted from the request)
+     * @param handler
+     *            the handler to be informed of the result
+     * @return true if the event was handled ("aborted", meaning a non-null result was obtained from
+     *         an event handler method)
+     */
+    boolean triggerEvent(String eventType, String[] context, ComponentEventHandler handler);
 }

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=463734&r1=463733&r2=463734
==============================================================================
--- 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 Oct 13 09:44:20 2006
@@ -22,15 +22,18 @@
 
 import org.apache.tapestry.BaseLocatable;
 import org.apache.tapestry.Binding;
+import org.apache.tapestry.ComponentEventHandler;
 import org.apache.tapestry.Link;
 import org.apache.tapestry.Location;
 import org.apache.tapestry.MarkupWriter;
 import org.apache.tapestry.internal.TapestryException;
 import org.apache.tapestry.internal.annotations.SuppressNullCheck;
 import org.apache.tapestry.internal.ioc.IOCUtilities;
+import org.apache.tapestry.internal.services.ComponentEventImpl;
 import org.apache.tapestry.internal.services.Instantiator;
 import org.apache.tapestry.model.ComponentModel;
 import org.apache.tapestry.model.ParameterModel;
+import org.apache.tapestry.runtime.ComponentEvent;
 import org.apache.tapestry.runtime.ComponentLifecycle;
 import org.apache.tapestry.runtime.LifecycleEvent;
 import org.apache.tapestry.runtime.PageLifecycleListener;
@@ -218,12 +221,7 @@
 
     public ComponentLifecycle getEmbeddedComponent(String embeddedId)
     {
-        ComponentPageElement embeddedElement = IOCUtilities.get(_children, embeddedId);
-
-        if (embeddedElement == null)
-            throw new IllegalArgumentException(StructureMessages.noSuchComponent(this, embeddedId));
-
-        return embeddedElement.getComponent();
+        return getEmbeddedElement(embeddedId).getComponent();
     }
 
     public String getId()
@@ -549,6 +547,38 @@
     public Link createActionLink(String action, boolean forForm, Object... context)
     {
         return _page.createActionLink(this, action, forForm, context);
+    }
+
+    public boolean triggerEvent(String eventType, String[] context, ComponentEventHandler handler)
+    {
+        ComponentEvent event = new ComponentEventImpl(eventType, _id, context, handler,
+                _typeCoercer);
+
+        ComponentPageElement component = this;
+
+        while (component != null)
+        {
+            component.getComponent().handleComponentEvent(event);
+
+            if (event.isAborted())
+                return true;
+            
+            component = component.getContainer();
+        }
+
+        // Made it to the top and never found a handler to provide a result value.
+
+        return false;
+    }
+
+    public ComponentPageElement getEmbeddedElement(String embeddedId)
+    {
+        ComponentPageElement embeddedElement = IOCUtilities.get(_children, embeddedId);
+
+        if (embeddedElement == null)
+            throw new IllegalArgumentException(StructureMessages.noSuchComponent(this, embeddedId));
+
+        return embeddedElement;
     }
 
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/Page.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/Page.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/Page.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/Page.java Fri Oct 13 09:44:20 2006
@@ -52,7 +52,10 @@
      */
     void setRootElement(ComponentPageElement component);
 
-    /** The root component of the page. This is the wrapper around the end developer's view of the page. */
+    /**
+     * The root component of the page. This is the wrapper around the end developer's view of the
+     * page.
+     */
     ComponentPageElement getRootElement();
 
     /**
@@ -86,6 +89,14 @@
 
     /** Returns the log of the root element. */
     Log getLog();
+
+    /**
+     * Retrieves a component by its nested id (a sequence of simple ids, separated by dots).
+     * 
+     * @throws IllegalArgumentException
+     *             if the nestedId does not correspond to a component
+     */
+    ComponentPageElement getComponentByNestedId(String nestedId);
 
     /**
      * Creates a link that will trigger behavior in a component within the page.

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/structure/PageImpl.java Fri Oct 13 09:44:20 2006
@@ -52,6 +52,16 @@
         return String.format("Page[%s %s]", _name, _locale);
     }
 
+    public ComponentPageElement getComponentByNestedId(String nestedId)
+    {
+        ComponentPageElement element = _rootElement;
+
+        for (String id : nestedId.split("\\."))
+            element = element.getEmbeddedElement(id);
+
+        return element;
+    }
+
     public String getName()
     {
         return _name;

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java Fri Oct 13 09:44:20 2006
@@ -50,11 +50,11 @@
      * @param <T>
      * @param index
      *            the index of the context value
-     * @param desiredType
+     * @param desiredTypeName
      *            the desired type
      * @param methodDescription
      *            the method for which the conversion will take place (used if reporting an error)
      * @return the coerced value (a wrapper type if the desired type is a primitive)
      */
-    <T> T coerceContext(int index, Class<T> desiredType, String methodDescription);
-}
+    Object coerceContext(int index, String desiredTypeName, String methodDescription);
+}
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentLifecycle.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentLifecycle.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentLifecycle.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentLifecycle.java Fri Oct 13 09:44:20 2006
@@ -15,6 +15,7 @@
 package org.apache.tapestry.runtime;
 
 import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.annotations.OnEvent;
 
 /**
  * Interface that defining the lifecycle of a component, within a page, allowing for callbacks into
@@ -80,4 +81,12 @@
      * complete.
      */
     void cleanupRender(MarkupWriter writer, LifecycleEvent<Boolean> event);
+
+    /**
+     * Invoked to handle a component event. Methods with the {@link OnEvent} annotation will be
+     * invoked until one returns a non-null value.
+     * 
+     * @param event
+     */
+    void handleComponentEvent(ComponentEvent event);
 }

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=463734&r1=463733&r2=463734
==============================================================================
--- 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 Oct 13 09:44:20 2006
@@ -103,6 +103,21 @@
     <T extends Annotation> T getFieldAnnotation(String fieldName, Class<T> annotationClass);
 
     /**
+     * Finds an annotation on a declared method.
+     * 
+     * @param <T>
+     *            constrains parameter and return value to Annotation types
+     * @param method
+     *            the method signature to search
+     * @param annotationClass
+     *            the type of annotation to access
+     * @return the annotation if present, or null otherwise
+     * @throws IllegalArgumentException
+     *             if the method signature does not correspond to a declared method
+     */
+    <T extends Annotation> T getMethodAnnotation(MethodSignature method, Class<T> annotationClass);
+
+    /**
      * Claims a field so as to ensure that only a single annotation is applied to any single field.
      * When a transformation occurs (driven by a field annotation), the first thing that occurs is
      * to claim the field, on behalf of the annotation.

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=463734&r1=463733&r2=463734
==============================================================================
--- 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 Oct 13 09:44:20 2006
@@ -19,6 +19,7 @@
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -40,6 +41,7 @@
 import org.apache.tapestry.internal.services.BindingSourceImpl;
 import org.apache.tapestry.internal.services.ComponentClassFactoryImpl;
 import org.apache.tapestry.internal.services.ComponentClassResolverImpl;
+import org.apache.tapestry.internal.services.ComponentEventDispatcher;
 import org.apache.tapestry.internal.services.ComponentInstantiatorSource;
 import org.apache.tapestry.internal.services.ComponentLifecycleMethodWorker;
 import org.apache.tapestry.internal.services.ComponentResourcesInjectionProvider;
@@ -47,13 +49,14 @@
 import org.apache.tapestry.internal.services.DefaultInjectionProvider;
 import org.apache.tapestry.internal.services.EnvironmentImpl;
 import org.apache.tapestry.internal.services.EnvironmentalWorker;
-import org.apache.tapestry.internal.services.HTMLDispatcher;
+import org.apache.tapestry.internal.services.PageRenderDispatcher;
 import org.apache.tapestry.internal.services.InfrastructureImpl;
 import org.apache.tapestry.internal.services.InfrastructureManagerImpl;
 import org.apache.tapestry.internal.services.InjectWorker;
 import org.apache.tapestry.internal.services.InjectionProvider;
 import org.apache.tapestry.internal.services.InternalModule;
 import org.apache.tapestry.internal.services.MarkupWriterImpl;
+import org.apache.tapestry.internal.services.OnEventWorker;
 import org.apache.tapestry.internal.services.PageResponseRenderer;
 import org.apache.tapestry.internal.services.ParameterWorker;
 import org.apache.tapestry.internal.services.RequestGlobalsImpl;
@@ -102,6 +105,10 @@
 
     private final PropertyShadowBuilder _shadowBuilder;
 
+    private final RequestPageCache _requestPageCache;
+
+    private final PageResponseRenderer _pageResponseRenderer;
+
     // Yes, you can inject services defined by this module into this module. The service proxy is
     // created without instantiating the module itself.
 
@@ -110,13 +117,17 @@
     PropertyShadowBuilder shadowBuilder, @InjectService("RequestGlobals")
     RequestGlobals requestGlobals, @InjectService("ApplicationGlobals")
     ApplicationGlobals applicationGlobals, @InjectService("tapestry.ioc.ChainBuilder")
-    ChainBuilder chainBuilder)
+    ChainBuilder chainBuilder, @InjectService("tapestry.internal.RequestPageCache")
+    RequestPageCache requestPageCache, @InjectService("tapestry.internal.PageResponseRenderer")
+    PageResponseRenderer pageResponseRenderer)
     {
         _pipelineBuilder = pipelineBuilder;
         _shadowBuilder = shadowBuilder;
         _requestGlobals = requestGlobals;
         _applicationGlobals = applicationGlobals;
         _chainBuilder = chainBuilder;
+        _requestPageCache = requestPageCache;
+        _pageResponseRenderer = pageResponseRenderer;
     }
 
     private <T> void add(Configuration<InfrastructureContribution> configuration,
@@ -203,14 +214,6 @@
         return _chainBuilder.build(ComponentClassTransformWorker.class, configuration);
     }
 
-    /** Dispatcher that recognizes full-page HTML documents by the .html extension. */
-    public Dispatcher buildHTMLDispatcher(@InjectService("tapestry.internal.PageResponseRenderer")
-    PageResponseRenderer renderer, @InjectService("tapestry.internal.RequestPageCache")
-    RequestPageCache cache)
-    {
-        return new HTMLDispatcher(renderer, cache);
-    }
-
     public HttpServletRequestHandler buildHttpServletRequestHandler(Log log,
             List<HttpServletRequestFilter> configuration, @InjectService("WebRequestHandler")
             final WebRequestHandler handler)
@@ -397,11 +400,16 @@
         configuration.add("infrastructure", infrastructure.getObjectProvider());
     }
 
-    public void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration,
-            @InjectService("HTMLDispatcher")
-            Dispatcher HTMLDispatcher)
+    public void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration)
     {
-        configuration.add("HTML", HTMLDispatcher);
+        configuration.add(
+                "HTML",
+                new PageRenderDispatcher(_pageResponseRenderer, _requestPageCache));
+
+        // This goes after the HTML one so that the "." in ".html" doesn't confuse it.
+
+        configuration.add("ComponentEvent", new ComponentEventDispatcher(_pageResponseRenderer,
+                _requestPageCache), "after:HTML");
     }
 
     public ComponentClassResolver buildComponentClassResolver(
@@ -502,6 +510,8 @@
         configuration.add("Component", new ComponentWorker());
         configuration.add("Environment", new EnvironmentalWorker(environment));
 
+        configuration.add("OnEvent", new OnEventWorker());
+
         // Workers for the component rendering state machine methods; this is in typical
         // execution order.
 
@@ -561,6 +571,7 @@
      * <li>Long to Boolean (true if long value is non zero)</li>
      * <li>Null to Boolean (always false)</li>
      * <li>Collection to Boolean (false if empty)</li>
+     * <li>Object to List (by wrapping as a singleton list)</li>
      * </ul>
      * 
      * @see #buildTypeCoercer(Collection, ComponentInstantiatorSource)
@@ -702,6 +713,14 @@
             public Boolean coerce(Collection input)
             {
                 return !input.isEmpty();
+            }
+        });
+
+        add(configuration, Object.class, List.class, new Coercion<Object, List>()
+        {
+            public List coerce(Object input)
+            {
+                return Collections.singletonList(input);
             }
         });
     }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TransformConstants.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TransformConstants.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TransformConstants.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TransformConstants.java Fri Oct 13 09:44:20 2006
@@ -18,6 +18,7 @@
 
 import org.apache.tapestry.MarkupWriter;
 import org.apache.tapestry.internal.annotations.Utility;
+import org.apache.tapestry.runtime.ComponentEvent;
 import org.apache.tapestry.runtime.LifecycleEvent;
 
 /**
@@ -33,6 +34,16 @@
     // component render states.
     private static final String[] LIFECYCLE_METHOD_PARAMETER_TYPES =
     { MarkupWriter.class.getName(), LifecycleEvent.class.getName() };
+
+    /**
+     * Signature for
+     * {@link org.apache.tapestry.runtime.ComponentLifecycle#handleComponentEvent(ComponentEvent event)
+     * 
+     * @see org.apache.tapestry.annotations.OnEvent
+     */
+    public static final MethodSignature HANDLE_COMPONENT_EVENT = new MethodSignature(
+            Modifier.PUBLIC, "void", "handleComponentEvent", new String[]
+            { ComponentEvent.class.getName() }, null);
 
     /**
      * Signature for

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TransformUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TransformUtils.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TransformUtils.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/services/TransformUtils.java Fri Oct 13 09:44:20 2006
@@ -18,6 +18,7 @@
 
 import java.util.Map;
 
+import org.apache.tapestry.internal.annotations.SuppressNullCheck;
 import org.apache.tapestry.internal.annotations.Utility;
 
 /**
@@ -32,18 +33,27 @@
 
     private static final Map<Class, PrimitiveTypeInfo> _classToInfo = newMap();
 
+    @SuppressNullCheck
     static class PrimitiveTypeInfo
     {
-        private Class _wrapperType;
+        private final Class _wrapperType;
 
-        private String _defaultValue;
+        private final String _unwrapperMethodName;
 
-        public PrimitiveTypeInfo(Class wrapperType, String defaultValue)
+        private final String _defaultValue;
+
+        public PrimitiveTypeInfo(Class wrapperType, String unwrapperMethodName, String defaultValue)
         {
             _wrapperType = wrapperType;
+            _unwrapperMethodName = unwrapperMethodName;
             _defaultValue = defaultValue;
         }
 
+        public String getUnwrapperMethodName()
+        {
+            return _unwrapperMethodName;
+        }
+
         public String getDefaultValue()
         {
             return _defaultValue;
@@ -57,25 +67,35 @@
 
     static
     {
-        add(boolean.class, Boolean.class, "false");
-        add(byte.class, Byte.class, "0");
-        add(char.class, Character.class, "0");
-        add(short.class, Short.class, "0");
-        add(int.class, Integer.class, "0");
-        add(long.class, Long.class, "0L");
-        add(float.class, Float.class, "0.0f");
-        add(double.class, Double.class, "0.0d");
+        add(boolean.class, Boolean.class, "booleanValue", "false");
+        add(byte.class, Byte.class, "byteValue", "0");
+        add(char.class, Character.class, "charValue", "0");
+        add(short.class, Short.class, "shortValue", "0");
+        add(int.class, Integer.class, "intValue", "0");
+        add(long.class, Long.class, "longValue", "0L");
+        add(float.class, Float.class, "floatValue", "0.0f");
+        add(double.class, Double.class, "doubleValue", "0.0d");
     }
 
-    private static void add(Class primitiveType, Class wrapperType, String defaultValue)
+    private static void add(Class primitiveType, Class wrapperType, String unwrapperMethodName,
+            String defaultValue)
     {
-        PrimitiveTypeInfo info = new PrimitiveTypeInfo(wrapperType, defaultValue);
+        PrimitiveTypeInfo info = new PrimitiveTypeInfo(wrapperType, unwrapperMethodName,
+                defaultValue);
 
         _classToInfo.put(primitiveType, info);
         _nameToInfo.put(primitiveType.getName(), info);
     }
 
     /**
+     * Returns true if the specified type is a primitive type.
+     */
+    public static boolean isPrimitive(String type)
+    {
+        return _nameToInfo.containsKey(type);
+    }
+
+    /**
      * Returns the name of wrapper type for a given input type. For primitive types, returns the
      * wrapper type. For other types, returns the input type name.
      * 
@@ -87,6 +107,21 @@
         PrimitiveTypeInfo info = _nameToInfo.get(type);
 
         return info == null ? type : info.getWrapperType().getName();
+    }
+
+    /**
+     * For primitive types, returns the method on the <em>wrapper type</em> that converts back to
+     * the primitive.
+     * 
+     * @param type
+     *            the primitive type
+     * @return the method of the corresponding wrapper type, or null if type is not a primitive type
+     */
+    public static String getUnwrapperMethodName(String type)
+    {
+        PrimitiveTypeInfo info = _nameToInfo.get(type);
+
+        return info == null ? null : info.getUnwrapperMethodName();
     }
 
     /**

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/BaseTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/BaseTestCase.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/BaseTestCase.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/test/BaseTestCase.java Fri Oct 13 09:44:20 2006
@@ -37,7 +37,6 @@
 import org.apache.tapestry.MarkupWriter;
 import org.apache.tapestry.Resource;
 import org.apache.tapestry.annotations.Parameter;
-import org.apache.tapestry.annotations.SetupRender;
 import org.apache.tapestry.internal.annotations.SuppressNullCheck;
 import org.apache.tapestry.internal.services.InjectionProvider;
 import org.apache.tapestry.ioc.Configuration;
@@ -443,9 +442,9 @@
     }
 
     protected final void train_findMethodsWithAnnotation(ClassTransformation tf,
-            List<MethodSignature> sigs)
+            Class<? extends Annotation> annotationType, List<MethodSignature> sigs)
     {
-        tf.findMethodsWithAnnotation(SetupRender.class);
+        tf.findMethodsWithAnnotation(annotationType);
         setReturnValue(sigs);
     }
 
@@ -499,7 +498,8 @@
         setReturnValue(Arrays.asList(names));
     }
 
-    protected <S, T> void train_coerce(TypeCoercer coercer, S input, Class<T> expectedType, T coercedValue)
+    protected <S, T> void train_coerce(TypeCoercer coercer, S input, Class<T> expectedType,
+            T coercedValue)
     {
         coercer.coerce(input, expectedType);
         setReturnValue(coercedValue);

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=463734&r1=463733&r2=463734
==============================================================================
--- 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 Fri Oct 13 09:44:20 2006
@@ -53,3 +53,4 @@
 page-name-unresolved=Unable to resolve class name %s to a logical page name.
 context-index-out-of-range=Method %s has more parameters than there are context values for this component event.
 exception-in-method-parameter=Exception in method %s, parameter #%d: %s
+component-event-is-aborted=Can not store result from invoking method %s, because an event result value has already been obtained from some other event handler method.
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ActionPage.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ActionPage.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ActionPage.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/integration/app1/pages/ActionPage.java Fri Oct 13 09:44:20 2006
@@ -1,6 +1,7 @@
 package org.apache.tapestry.integration.app1.pages;
 
 import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.OnEvent;
 
 /**
  * @author Howard M. Lewis Ship
@@ -8,5 +9,16 @@
 @ComponentClass
 public class ActionPage
 {
+    private boolean _clicked;
 
+    public boolean getClicked()
+    {
+        return _clicked;
+    }
+
+    @OnEvent
+    void click()
+    {
+        _clicked = true;
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/bindings/DefaultComponentLifecyle.java Fri Oct 13 09:44:20 2006
@@ -16,6 +16,7 @@
 
 import org.apache.tapestry.ComponentResources;
 import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.runtime.ComponentEvent;
 import org.apache.tapestry.runtime.ComponentLifecycle;
 import org.apache.tapestry.runtime.LifecycleEvent;
 
@@ -68,4 +69,8 @@
     {
     }
 
+    public void handleComponentEvent(ComponentEvent event)
+    {
+
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentEventImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentEventImplTest.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentEventImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentEventImplTest.java Fri Oct 13 09:44:20 2006
@@ -1,7 +1,11 @@
 package org.apache.tapestry.internal.services;
 
+import org.apache.tapestry.ComponentEventHandler;
 import org.apache.tapestry.internal.test.InternalBaseTestCase;
 import org.apache.tapestry.runtime.ComponentEvent;
+import org.apache.tapestry.services.TypeCoercer;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 /**
@@ -9,10 +13,24 @@
  */
 public class ComponentEventImplTest extends InternalBaseTestCase
 {
+    private TypeCoercer _coercer;
+
+    @BeforeClass
+    public void setup_coercer()
+    {
+        _coercer = getService("tapestry.TypeCoercer", TypeCoercer.class);
+    }
+
+    @AfterClass
+    public void cleanup_coercer()
+    {
+        _coercer = null;
+    }
+
     @Test
     public void matches_on_event_type()
     {
-        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, null, null);
+        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, null, _coercer);
 
         assertTrue(event.matchesByEventType(new String[]
         { "foo", "eventType", "bar" }));
@@ -23,12 +41,138 @@
     @Test
     public void matches_on_component_id()
     {
-        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, null, null);
+        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, null, _coercer);
 
         assertTrue(event.matchesByComponentId(null, new String[]
         { "foo", "someId", "bar" }));
         assertFalse(event.matchesByComponentId(null, new String[]
         { "foo", "bar" }));
+    }
+
+    @Test
+    public void coerce_context()
+    {
+        ComponentEvent event = new ComponentEventImpl("eventType", "someId", new String[]
+        { "27" }, null, _coercer);
+
+        assertEquals(event.coerceContext(0, "java.lang.Integer", "method descrip"), new Integer(27));
+    }
+
+    @Test
+    public void coerce_when_not_enough_context()
+    {
+        ComponentEvent event = new ComponentEventImpl("eventType", "someId", new String[]
+        { "27" }, null, _coercer);
+
+        try
+        {
+            event.coerceContext(1, "java.lang.Integer", "foo.Bar.baz()");
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Method foo.Bar.baz() has more parameters than there are context values for this component event.");
+        }
+    }
+
+    @Test
+    public void unable_to_coerce()
+    {
+        ComponentEvent event = new ComponentEventImpl("eventType", "someId", new String[]
+        { "abc" }, null, _coercer);
+
+        try
+        {
+            event.coerceContext(0, "java.lang.Integer", "foo.Bar.baz()");
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            // Different JVMs will report the conversion error slightly differently,
+            // so we don't try to check that part of the error message.
+            
+            assertTrue(ex.getMessage().startsWith(
+                    "Exception in method foo.Bar.baz(), parameter #1:"));
+        }
+    }
+
+    @Test
+    public void store_result()
+    {
+        Object result = new Object();
+        String methodDescription = "foo.Bar.baz()";
+
+        ComponentEventHandler handler = newComponentEventHandler();
+
+        handler.handleResult(result, methodDescription);
+
+        replay();
+
+        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, handler,
+                _coercer);
+
+        assertFalse(event.isAborted());
+
+        assertTrue(event.storeResult(result, methodDescription));
+
+        assertTrue(event.isAborted());
+
+        verify();
+    }
+
+    @Test
+    public void store_null_result_does_not_abort_or_invoke_handler()
+    {
+        String methodDescription = "foo.Bar.baz()";
+
+        ComponentEventHandler handler = newComponentEventHandler();
+
+        replay();
+
+        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, handler,
+                _coercer);
+
+        assertFalse(event.storeResult(null, methodDescription));
+
+        assertFalse(event.isAborted());
+
+        verify();
+    }
+
+    protected final ComponentEventHandler newComponentEventHandler()
+    {
+        return newMock(ComponentEventHandler.class);
+    }
+
+    @Test
+    public void store_result_when_aborted_is_failure()
+    {
+        Object result = new Object();
+        String methodDescription = "foo.Bar.baz()";
+
+        ComponentEventHandler handler = newComponentEventHandler();
+
+        handler.handleResult(result, methodDescription);
+
+        replay();
+
+        ComponentEvent event = new ComponentEventImpl("eventType", "someId", null, handler,
+                _coercer);
+
+        event.storeResult(result, methodDescription);
+
+        try
+        {
+            event.storeResult(null, "foo.Bar.biff()");
+            unreachable();
+        }
+        catch (IllegalStateException ex)
+        {
+            assertEquals(ex.getMessage(), ServicesMessages
+                    .componentEventIsAborted("foo.Bar.biff()"));
+        }
 
+        verify();
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/ComponentLifecycleMethodWorkerTest.java Fri Oct 13 09:44:20 2006
@@ -42,7 +42,7 @@
 
         List<MethodSignature> sigs = newList();
 
-        train_findMethodsWithAnnotation(tf, sigs);
+        train_findMethodsWithAnnotation(tf, SetupRender.class, sigs);
 
         replay();
 
@@ -64,7 +64,7 @@
 
         sigs.add(new MethodSignature("aMethod"));
 
-        train_findMethodsWithAnnotation(tf, sigs);
+        train_findMethodsWithAnnotation(tf, SetupRender.class, sigs);
 
         train_extendMethod(
                 tf,
@@ -92,7 +92,7 @@
         sigs.add(new MethodSignature(Modifier.PUBLIC, "void", "aMethod", new String[]
         { MarkupWriter.class.getName() }, null));
 
-        train_findMethodsWithAnnotation(tf, sigs);
+        train_findMethodsWithAnnotation(tf, SetupRender.class, sigs);
 
         train_extendMethod(
                 tf,
@@ -120,7 +120,7 @@
 
         sigs.add(new MethodSignature(Modifier.PROTECTED, "boolean", "aMethod", null, null));
 
-        train_findMethodsWithAnnotation(tf, sigs);
+        train_findMethodsWithAnnotation(tf, SetupRender.class, sigs);
 
         train_getClassName(tf, "biff.Baz");
 
@@ -153,7 +153,7 @@
         sigs.add(new MethodSignature(Modifier.PUBLIC, "void", "bMethod", new String[]
         { MarkupWriter.class.getName() }, null));
 
-        train_findMethodsWithAnnotation(tf, sigs);
+        train_findMethodsWithAnnotation(tf, SetupRender.class, sigs);
         train_getClassName(tf, "foo.Bar");
 
         train_extendMethod(

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=463734&r1=463733&r2=463734
==============================================================================
--- 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 Oct 13 09:44:20 2006
@@ -33,6 +33,7 @@
 
 import org.apache.commons.logging.Log;
 import org.apache.tapestry.annotations.ComponentClass;
+import org.apache.tapestry.annotations.OnEvent;
 import org.apache.tapestry.annotations.Retain;
 import org.apache.tapestry.annotations.SetupRender;
 import org.apache.tapestry.internal.InternalComponentResources;
@@ -43,6 +44,7 @@
 import org.apache.tapestry.internal.transform.pages.BasicComponent;
 import org.apache.tapestry.internal.transform.pages.ChildClassInheritsAnnotation;
 import org.apache.tapestry.internal.transform.pages.ClaimedFields;
+import org.apache.tapestry.internal.transform.pages.EventHandlerTarget;
 import org.apache.tapestry.internal.transform.pages.ParentClass;
 import org.apache.tapestry.internal.transform.pages.TargetObject;
 import org.apache.tapestry.internal.transform.pages.TargetObjectSubclass;
@@ -829,6 +831,51 @@
         assertEquals(ct.newMemberName("_$myLong"), "_$myLong_0");
         assertEquals(ct.newMemberName("_$myStatic"), "_$myStatic_0");
         assertEquals(ct.newMemberName("_$myProtected"), "_$myProtected_0");
+
+        verify();
+    }
+
+    @Test
+    public void find_annotation_in_method() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(EventHandlerTarget.class, log);
+
+        OnEvent annotation = ct.getMethodAnnotation(new MethodSignature("handler"), OnEvent.class);
+
+        // Check that the attributes of the annotation match the expectation.
+        
+        assertEquals(annotation.value(), new String[]
+        { "fred", "barney" });
+        assertEquals(annotation.component(), new String[]
+        { "alpha", "beta" });
+
+        verify();
+    }
+
+    @Test
+    public void find_annotation_in_unknown_method() throws Exception
+    {
+        Log log = newLog();
+
+        replay();
+
+        ClassTransformation ct = createClassTransformation(ParentClass.class, log);
+
+        try
+        {
+            ct.getMethodAnnotation(new MethodSignature("foo"), OnEvent.class);
+            unreachable();
+        }
+        catch (IllegalArgumentException ex)
+        {
+            assertEquals(
+                    ex.getMessage(),
+                    "Class org.apache.tapestry.internal.transform.pages.ParentClass does not declare method 'public void foo()'.");
+        }
 
         verify();
     }

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java?view=auto&rev=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java Fri Oct 13 09:44:20 2006
@@ -0,0 +1,410 @@
+package org.apache.tapestry.internal.services;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.tapestry.annotations.OnEvent;
+import org.apache.tapestry.internal.test.InternalBaseTestCase;
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.MethodSignature;
+import org.apache.tapestry.services.TransformConstants;
+import org.apache.tapestry.util.CollectionFactory;
+import org.testng.annotations.Test;
+
+/**
+ * I'd prefer to test these in terms of the behavior of the final class, rather than the generated
+ * Javassist method bodies, but that'll have to be saved for later integration tests.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public class OnEventWorkerTest extends InternalBaseTestCase
+{
+    private static final String BOILERPLATE_2 = "String methodName = null;";
+
+    private static final String BOILERPLATE_1 = "if ($1.isAborted()) return;";
+
+    @Test
+    public void no_methods_with_annotation()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+
+        List<MethodSignature> methods = CollectionFactory.newList();
+        train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+        replay();
+
+        new OnEventWorker().transform(ct, model);
+
+        verify();
+    }
+
+    @Test
+    public void minimal_case()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        OnEvent annotation = newOnEvent();
+
+        MethodSignature signature = new MethodSignature("foo");
+
+        List<MethodSignature> methods = Collections.singletonList(signature);
+        train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+        train_getMethodAnnotation(ct, signature, OnEvent.class, annotation);
+
+        train_value(annotation, new String[0]);
+        train_component(annotation, new String[0]);
+
+        train_getClassName(ct, "foo.Bar");
+
+        train_extendMethod(
+                ct,
+                TransformConstants.HANDLE_COMPONENT_EVENT,
+                "{",
+                BOILERPLATE_1,
+                BOILERPLATE_2,
+                "methodName = \"foo.Bar.foo()\";",
+                "foo();",
+                "}");
+
+        replay();
+
+        new OnEventWorker().transform(ct, model);
+
+        verify();
+    }
+
+    @Test
+    public void filter_by_event_type()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        OnEvent annotation = newOnEvent();
+
+        String[] eventTypes = new String[]
+        { "gnip", "gnop" };
+
+        MethodSignature signature = new MethodSignature("foo");
+
+        List<MethodSignature> methods = Collections.singletonList(signature);
+        train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+        train_getMethodAnnotation(ct, signature, OnEvent.class, annotation);
+
+        train_value(annotation, eventTypes);
+
+        train_addInjectedField(ct, String[].class, "eventTypes", eventTypes, "_v");
+
+        train_component(annotation, new String[0]);
+
+        train_getClassName(ct, "foo.Bar");
+
+        train_extendMethod(
+                ct,
+                TransformConstants.HANDLE_COMPONENT_EVENT,
+                "{",
+                BOILERPLATE_1,
+                BOILERPLATE_2,
+                "if ($1.matchesByEventType(_v))",
+                "{",
+                "methodName = \"foo.Bar.foo()\";",
+                "foo();",
+                "}",
+                "}");
+
+        replay();
+
+        new OnEventWorker().transform(ct, model);
+
+        verify();
+    }
+
+    @Test
+    public void filter_by_component_id()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        OnEvent annotation = newOnEvent();
+
+        String[] componentIds = new String[]
+        { "zork" };
+
+        MethodSignature signature = new MethodSignature("foo");
+
+        List<MethodSignature> methods = Collections.singletonList(signature);
+        train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+        train_getMethodAnnotation(ct, signature, OnEvent.class, annotation);
+
+        train_value(annotation, new String[0]);
+        train_component(annotation, componentIds);
+
+        train_addInjectedField(ct, java.lang.String[].class, "componentIds", componentIds, "_ids");
+
+        train_getResourcesFieldName(ct, "_res");
+
+        train_getClassName(ct, "foo.Bar");
+
+        train_extendMethod(
+                ct,
+                TransformConstants.HANDLE_COMPONENT_EVENT,
+                "{",
+                BOILERPLATE_1,
+                BOILERPLATE_2,
+                "if ($1.matchesByComponentId(_res, _ids))",
+                "{",
+                "methodName = \"foo.Bar.foo()\";",
+                "foo();",
+                "}",
+                "}");
+
+        replay();
+
+        new OnEventWorker().transform(ct, model);
+
+        verify();
+    }
+
+    @Test
+    public void filter_by_both()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        OnEvent annotation = newOnEvent();
+
+        String[] eventTypes = new String[]
+        { "gnip", "gnop" };
+        String[] componentIds = new String[]
+        { "zork" };
+
+        MethodSignature signature = new MethodSignature("foo");
+
+        List<MethodSignature> methods = Collections.singletonList(signature);
+        train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+        train_getMethodAnnotation(ct, signature, OnEvent.class, annotation);
+
+        train_value(annotation, eventTypes);
+
+        train_addInjectedField(ct, java.lang.String[].class, "eventTypes", eventTypes, "_v");
+
+        train_component(annotation, componentIds);
+
+        train_addInjectedField(ct, java.lang.String[].class, "componentIds", componentIds, "_ids");
+
+        train_getResourcesFieldName(ct, "_res");
+
+        train_getClassName(ct, "foo.Bar");
+
+        train_extendMethod(
+                ct,
+                TransformConstants.HANDLE_COMPONENT_EVENT,
+                "{",
+                BOILERPLATE_1,
+                BOILERPLATE_2,
+                "if ($1.matchesByEventType(_v))",
+                "{",
+                "if ($1.matchesByComponentId(_res, _ids))",
+                "{",
+                "methodName = \"foo.Bar.foo()\";",
+                "foo();",
+                "}",
+                "}",
+                "}");
+
+        replay();
+
+        new OnEventWorker().transform(ct, model);
+
+        verify();
+
+    }
+
+    @Test
+    public void method_with_non_void_return_value()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        OnEvent annotation = newOnEvent();
+
+        MethodSignature signature = new MethodSignature(Modifier.PRIVATE, "java.lang.String",
+                "foo", null, null);
+
+        List<MethodSignature> methods = Collections.singletonList(signature);
+        train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+        train_getMethodAnnotation(ct, signature, OnEvent.class, annotation);
+
+        train_value(annotation, new String[0]);
+        train_component(annotation, new String[0]);
+
+        train_getClassName(ct, "foo.Bar");
+
+        train_extendMethod(
+                ct,
+                TransformConstants.HANDLE_COMPONENT_EVENT,
+                "{",
+                BOILERPLATE_1,
+                BOILERPLATE_2,
+                "methodName = \"foo.Bar.foo()\";",
+                "if ($1.storeResult(foo(), methodName) return;",
+                "}");
+
+        replay();
+
+        new OnEventWorker().transform(ct, model);
+
+        verify();
+    }
+
+    @Test
+    public void method_with_non_primitive_parameter()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        OnEvent annotation = newOnEvent();
+
+        MethodSignature signature = new MethodSignature(Modifier.PRIVATE, "void", "foo",
+                new String[]
+                { "java.lang.String" }, null);
+
+        List<MethodSignature> methods = Collections.singletonList(signature);
+        train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+        train_getMethodAnnotation(ct, signature, OnEvent.class, annotation);
+
+        train_value(annotation, new String[0]);
+        train_component(annotation, new String[0]);
+
+        train_getClassName(ct, "foo.Bar");
+
+        train_extendMethod(
+                ct,
+                TransformConstants.HANDLE_COMPONENT_EVENT,
+                "{",
+                BOILERPLATE_1,
+                BOILERPLATE_2,
+                "methodName = \"foo.Bar.foo(java.lang.String)\";",
+                "foo($1.coerceContext(0, \"java.lang.String\", methodName));",
+                "}");
+
+        replay();
+
+        new OnEventWorker().transform(ct, model);
+
+        verify();
+    }
+
+    @Test
+    public void method_with_primitive_parameter()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        OnEvent annotation = newOnEvent();
+
+        MethodSignature signature = new MethodSignature(Modifier.PRIVATE, "void", "foo",
+                new String[]
+                { "boolean" }, null);
+
+        List<MethodSignature> methods = Collections.singletonList(signature);
+        train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+        train_getMethodAnnotation(ct, signature, OnEvent.class, annotation);
+
+        train_value(annotation, new String[0]);
+        train_component(annotation, new String[0]);
+
+        train_getClassName(ct, "foo.Bar");
+
+        train_extendMethod(
+                ct,
+                TransformConstants.HANDLE_COMPONENT_EVENT,
+                "{",
+                BOILERPLATE_1,
+                BOILERPLATE_2,
+                "methodName = \"foo.Bar.foo(boolean)\";",
+                "foo(((java.lang.Boolean)$1.coerceContext(0, \"java.lang.Boolean\", methodName)).booleanValue());",
+                "}");
+
+        replay();
+
+        new OnEventWorker().transform(ct, model);
+
+        verify();
+
+    }
+
+    @Test
+    public void method_with_multiple_parameters()
+    {
+        ClassTransformation ct = newClassTransformation();
+        MutableComponentModel model = newMutableComponentModel();
+        OnEvent annotation = newOnEvent();
+
+        MethodSignature signature = new MethodSignature(Modifier.PRIVATE, "void", "foo",
+                new String[]
+                { "java.lang.String", "java.lang.Integer" }, null);
+
+        List<MethodSignature> methods = Collections.singletonList(signature);
+        train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+        train_getMethodAnnotation(ct, signature, OnEvent.class, annotation);
+
+        train_value(annotation, new String[0]);
+        train_component(annotation, new String[0]);
+
+        train_getClassName(ct, "foo.Bar");
+
+        train_extendMethod(
+                ct,
+                TransformConstants.HANDLE_COMPONENT_EVENT,
+                "{",
+                BOILERPLATE_1,
+                BOILERPLATE_2,
+                "methodName = \"foo.Bar.foo(java.lang.String, java.lang.Integer)\";",
+                "foo($1.coerceContext(0, \"java.lang.String\", methodName), ",
+                "$1.coerceContext(1, \"java.lang.Integer\", methodName));",
+                "}");
+
+        replay();
+
+        new OnEventWorker().transform(ct, model);
+
+        verify();
+
+    }
+
+    protected final OnEvent newOnEvent()
+    {
+        return newMock(OnEvent.class);
+    }
+
+    protected final void train_getClassName(ClassTransformation ct, String className)
+    {
+        ct.getClassName();
+        setReturnValue(className);
+    }
+
+    protected final void train_value(OnEvent annotation, String[] values)
+    {
+        annotation.value();
+        setReturnValue(values);
+    }
+
+    protected final void train_component(OnEvent annotation, String[] values)
+    {
+        annotation.component();
+        setReturnValue(values);
+    }
+
+    protected final <T extends Annotation> void train_getMethodAnnotation(ClassTransformation ct,
+            MethodSignature signature, Class<T> annotationClass, T annotation)
+    {
+        ct.getMethodAnnotation(signature, annotationClass);
+        setReturnValue(annotation);
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/TypeCoercerImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/TypeCoercerImplTest.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/TypeCoercerImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/TypeCoercerImplTest.java Fri Oct 13 09:44:20 2006
@@ -16,8 +16,9 @@
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
-import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.tapestry.internal.annotations.SuppressNullCheck;
 import org.apache.tapestry.internal.test.InternalBaseTestCase;
@@ -92,7 +93,7 @@
     {
         try
         {
-            _coercer.coerce("", Collection.class);
+            _coercer.coerce("", Map.class);
             unreachable();
         }
         catch (IllegalArgumentException ex)
@@ -100,7 +101,7 @@
             assertTrue(ex
                     .getMessage()
                     .contains(
-                            "Could not find a coercion from type java.lang.String to type java.util.Collection"));
+                            "Could not find a coercion from type java.lang.String to type java.util.Map"));
         }
     }
 
@@ -117,6 +118,7 @@
         String bigDecimalValue = "12345656748352435842385234598234958234574358723485.35843534285293857298457234587";
         String bigIntegerValue = "12384584574874385743";
 
+        Object object = new Object();
         // Over time, some of these may evolve from testing specific tuples to
         // compound tuples (built around specific tuples).
 
@@ -153,6 +155,8 @@
                 // BigInteger
                 { new BigInteger("12345678"), Long.class, 12345678l },
                 { -12345678l, BigInteger.class, new BigInteger("-12345678") },
+                // Object --> List
+                { object, List.class, Collections.singletonList(object) },
 
         };
     }

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/pages/EventHandlerTarget.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/pages/EventHandlerTarget.java?view=auto&rev=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/pages/EventHandlerTarget.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/transform/pages/EventHandlerTarget.java Fri Oct 13 09:44:20 2006
@@ -0,0 +1,16 @@
+package org.apache.tapestry.internal.transform.pages;
+
+import org.apache.tapestry.annotations.OnEvent;
+
+/**
+ * @author Howard M. Lewis Ship
+ */
+public class EventHandlerTarget
+{
+    @OnEvent(value =
+    { "fred", "barney" }, component =
+    { "alpha", "beta" })
+    public void handler()
+    {
+    }
+}

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/services/TransformUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/services/TransformUtilsTest.java?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/services/TransformUtilsTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/services/TransformUtilsTest.java Fri Oct 13 09:44:20 2006
@@ -15,8 +15,10 @@
 package org.apache.tapestry.services;
 
 import static org.apache.tapestry.services.TransformUtils.getDefaultValue;
+import static org.apache.tapestry.services.TransformUtils.getUnwrapperMethodName;
 import static org.apache.tapestry.services.TransformUtils.getWrapperType;
 import static org.apache.tapestry.services.TransformUtils.getWrapperTypeName;
+import static org.apache.tapestry.services.TransformUtils.isPrimitive;
 
 import java.util.Map;
 
@@ -47,5 +49,20 @@
     {
         assertEquals(getDefaultValue("long"), "0L");
         assertEquals(getDefaultValue("java.util.Map"), "null");
+    }
+
+    @Test
+    public void is_primitive()
+    {
+        assertTrue(isPrimitive("int"));
+        assertFalse(isPrimitive("java.lang.Integer"));
+    }
+
+    @Test
+    public void unwrapper_method_name()
+    {
+        assertEquals(getUnwrapperMethodName("boolean"), "booleanValue");
+        assertEquals(getUnwrapperMethodName("int"), "intValue");
+        assertNull(getUnwrapperMethodName("java.lang.Integer"));
     }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ActionPage.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ActionPage.html?view=diff&rev=463734&r1=463733&r2=463734
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ActionPage.html (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/resources/org/apache/tapestry/integration/app1/pages/ActionPage.html Fri Oct 13 09:44:20 2006
@@ -1,7 +1,10 @@
 <t:comp type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
     <p>
-        <t:comp id="action" type="ActionLink">
-            This link is an action.
-        </t:comp>
-    </p>
+        <t:comp id="action" type="ActionLink"> Click here! </t:comp>
+    </p>
+
+    <t:comp type="If" test="prop:clicked">
+        <p> You clicked the link! </p>
+    </t:comp>
+
 </t:comp>