You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by ro...@apache.org on 2009/07/18 00:53:32 UTC

svn commit: r795264 [1/3] - in /tapestry/tapestry5/trunk: src/site/apt/dev/ src/site/apt/guide/ tapestry-core/src/main/java/org/apache/tapestry5/annotations/ tapestry-core/src/main/java/org/apache/tapestry5/internal/ tapestry-core/src/main/java/org/apa...

Author: robertdzeigler
Date: Fri Jul 17 22:53:30 2009
New Revision: 795264

URL: http://svn.apache.org/viewvc?rev=795264&view=rev
Log:
TAP5-103: provide access to component parameters from within mixins
TAP5-748: NPE when defining a component using just t:id and no type or @Component annotation
TAP5-777: Tapestry should ensure that mixins are applied in a deterministic order.
TAP5-786: Exception messages in the pageload packages should be localized
TAP5-787: AbstractIntegrationTest's "assertTextPresent" only checks the first value


Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/BindParameter.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangeListener.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangedEvent.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageloadMessages.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/BindParameterWorker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/pageload/
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/pageload/PageloadStrings.properties
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/BindParameterComponent.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/BindParameterComponentContainer.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/TextOnlyOnDisabledTextField.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/mixins/EchoAfter.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/mixins/EchoAfter2.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/mixins/EchoValue.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/mixins/EchoValue2.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/mixins/EchoValue3.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/mixins/TextOnlyOnDisabled.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BindParameterDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BindParameterNoSuchParameter.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/BindParameterOnComponent.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MissingComponentClassException.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/MixinOrderingDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/BindParameterComponentContainer.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/BindParameterDemo.tml
      - copied, changed from r786676, tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/ParameterDefault.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/BindParameterNoSuchParameter.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/BindParameterOnComponent.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/MissingComponentClassException.tml
      - copied, changed from r786676, tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/BadTemplate.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/MixinOrderingDemo.tml
Modified:
    tapestry/tapestry5/trunk/src/site/apt/dev/bible.apt
    tapestry/tapestry5/trunk/src/site/apt/guide/mixins.apt
    tapestry/tapestry5/trunk/src/site/apt/guide/rendering.apt
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixin.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/MixinClasses.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixins.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResources.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResourcesCommon.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterAccess.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableComponentModelImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableEmbeddedComponentModelImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/ParameterModelImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/CompositeRenderCommand.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/EmbeddedComponentAssemblerImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStreamImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/parser/ExtensionPointToken.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StaxTemplateParser.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ComponentWorker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/MixinWorker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ParameterWorker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/TransformMessages.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/ComponentModel.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/EmbeddedComponentModel.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/MutableComponentModel.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/MutableEmbeddedComponentModel.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/model/ParameterModel.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/structure/StructureStrings.properties
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/internal/transform/TransformStrings.properties
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/ClientPersistenceDemo.tml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/IntegrationTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/ClientPersistenceDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/TemplateParserImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/transform/MixinWorkerTest.java
    tapestry/tapestry5/trunk/tapestry-test/src/main/java/org/apache/tapestry5/test/AbstractIntegrationTestSuite.java

Modified: tapestry/tapestry5/trunk/src/site/apt/dev/bible.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/dev/bible.apt?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/dev/bible.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/dev/bible.apt Fri Jul 17 22:53:30 2009
@@ -122,7 +122,7 @@
 
 Evolving Interfaces
 
-  Tapestry users interfaces quite extensively.
+  Tapestry uses interfaces quite extensively.
 
   Interfaces fall into two categories: service interfaces called by user code, and interfaces implemented by user code.
 

Modified: tapestry/tapestry5/trunk/src/site/apt/guide/mixins.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/mixins.apt?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/mixins.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/mixins.apt Fri Jul 17 22:53:30 2009
@@ -93,7 +93,7 @@
   attribute of the annotation will be used to determine the mixin class name:
   
 +---+
-public class AutocompleteField extendes TextField
+public class AutocompleteField extends TextField
 {
   @Mixin("Autocomplete")
   private Object autocompleteMixin;
@@ -123,7 +123,76 @@
   private TextField userId;
 +-----+
 
+Binding the parameter of the core component
+
+  It is sometimes desirable to access the current value of a parameter defined in the component associated with the
+  mixin. For example: normally, when the textfield component is marked disabled, it renders a text field with a disabled
+  attribute, but you want it to output the (plaintext) value when disabled.  A mixin for this purpose would need access
+  to at least the disabled, and value parameters, and possibly the translate parameter (for a client-side representation).
+  You can access the disabled parameter via @InjectContainer and checking isDisabled on the field, but textfield
+  currently provides no access to value or translate.  In this case, you can bind the core-component parameter using the
+  @BindParameter annotation:
+
++-----+
+  public class MyMixin
+  {
+    @BindParameter
+    private boolean disabled;
+
+    @BindParameter
+    private FieldTranslator translate;
+
+    @BindParameter
+    private Object value;
+
+    Boolean beginRender(MarkupWriter writer)
+    {
+        ...
+        if (disabled)
+        {
+           ...
+           String stringValue = translate.toClient(value));
+           ...
+        }
+        ...
+    }
+    ....
++-----+
+
+  Tapestry will "link" the disabled, translate, and value fields above to parameters of the same name on the
+  associated component.  The fields are not parameters to the mixin, but local copies
+  of the component parameter.  They are cached only if the associated component parameter is cached.  They are read-write,
+  and Tapestry handles synchronizing the value between mixins and the associated component such that even with a cached
+  parameter, components and mixins will share the same value for a given parameter/bound-parameter during render.
+  Only declared parameters of the associated components may be bound.
   
+  By default, Tapestry will bind the parameter with the same name as the field. You can explicitly declare the parameter
+  to bind via the value attribute:
+
++-----+
+  @BindParameter("translate")
+  private FieldTranslator translator;
++-----+
+
+  In same cases, a mixin will be used on different components using different names for a similar parameter type.
+  For instance, BeanEditor has an "object" parameter; most form fields have a "value" parameter, and Grid has a
+  "source" parameter.  These parameters have different names but share the feature of being the "principle" parameter
+  on which the components are acting.  A mixin useable by all three components can specify multiple potential
+  parameter values to bind.  The first value that matches a declared parameter of the associated component will be used:
+
++-----+
+public class MyMixin
+{
+  ...
+  @BindParameter({"value","object","source"})
+  private Object principalObject;
+  ...
+}
++-----+
+
+  "MyMixin" can be used on a textfield (principalObject is bound to "value"), on BeanEditor or BeanDisplay
+  (principalObject is bound to "object"), or on Grid or Loop (principalObject is bound to "source").
+
 Render Phase Ordering
 
   All mixins for a component execute their render phase methods <before> the component's render phase
@@ -132,5 +201,44 @@
   
   Exception: Mixins whose class is annotated with
   {{{../apidocs/org/apache/tapestry5/annotations/MixinAfter.html}MixinAfter}} are ordered
-  <after> the component, not before.  
-  
\ No newline at end of file
+  <after> the component, not before.
+
+  Withing a given phase and class (@MixinAfter vs. mixin before), mixin ordering is determined by the ordering
+  constraints specified in the mixin definitions.  The constraint definitions follow the same conventions as
+  {{{../tapestry-ioc/order.html}ordered service configurations}}.  How you specify the constraints depends
+  on how the mixin is specified.
+
+  Implementation Mixin:
+
++-----+
+  @Mixin("Autocomplete",order={"before:DiscardBody","after:RenderDisabled"}
+  private TextField userId;
++-----+
+
+  Template-specified instance mixins:
+
++-----+
+  \<input t:id="myfield" t:mixins="autocomplete::before:discardbody;after:renderdisabled,
+          defaultfromcookie::before:autocomplete"/\>
++-----+
+
+  @Mixins-specified instance mixins:
+
++-----+
+  @Component(...)
+  @Mixins("Autocomplete::before:discardbody;after:renderdisabled","DefaultFromCookie::before:autocomplete"))
+  private TextField userId;
++-----+
+
+  @MixinClasses-specified instance mixins:
+
++-----+
+  @Component(...)
+  @MixinClasses(value={Autocomplete.class,DefaultFromCookie.class},
+                order={"before:discardbody;after:renderdisabled","before:autocomplete")
++-----+
+
+  The ordering is always specified in terms of the order of the "forward" rendering process (setupRender,
+  beginRender, etc.). When the "reverse" rendering phases (afterRender, etc.) occur, the mixin order is exactly reversed.
+  Mixins which have no associated ordering constraints will be ordered in a manner which is consistent with the
+  specified constraints for all other mixins, but is otherwise unspecified.

Modified: tapestry/tapestry5/trunk/src/site/apt/guide/rendering.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/rendering.apt?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/rendering.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/rendering.apt Fri Jul 17 22:53:30 2009
@@ -289,12 +289,14 @@
   the component's render phase methods.  If a mixin extends from a base class, the mixin's
   parent class methods execute before the mixin subclass' render phase methods.
   
-  The order in which the mixins execute is not defined at this time.
-  
   Exception: Mixins whose class is annotated with
   {{{../apidocs/org/apache/tapestry5/annotations/MixinAfter.html}MixinAfter}} are ordered
   <after> the component, not before.
-  
+
+  The order in which the mixins of a given class (@MixinAfter or mixins before) execute is determined
+  by the ordering constraints specified for the mixins. If no constrains are provided, the order is undefined.
+  See {{{mixins.html}component mixins} for more details.
+
 * Parents before Child
 
   Ordering is always parent-first.  Methods defined in the parent class are always invoked

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/BindParameter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/BindParameter.java?rev=795264&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/BindParameter.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/BindParameter.java Fri Jul 17 22:53:30 2009
@@ -0,0 +1,49 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.annotations;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Designates a field in a mixin which is bound to the parameter of the containing
+ * component corresponding to the value of the annotation. If no value is specified,
+ * the bound parameter name is assumed to match the field name of the mixin.
+ *
+ * For example, a mixin intended to work with form fields would define a field named
+ * "value", marked by this annotation.  The user-variable bound to the component's value
+ * parameter would ultimately be bound in a chain:
+ * uservariable <=> mixin.value <=> component.value.
+ * Changes to any one value in the chain will be propagated accordingly.
+ *
+ * @since 5.2.0.0
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface BindParameter
+{
+
+    /**
+     * @return the name of the mixin bound-parameter, exactly as for the Parameter annotation.
+     */
+    String name() default "";
+    /**
+     * @return the name(s) of the parent parameter to bind. Defaults to the name of the mixin field. If more than one
+     *         name is specified, the first name matching a declared parameter of the core component will be used.
+     */
+    String[] value() default {""};
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixin.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixin.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixin.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixin.java Fri Jul 17 22:53:30 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -36,4 +36,12 @@
      * the mixin class to instantiate and attach to the component.
      */
     String value() default "";
+
+    /**
+     * Defines an ordering constraint for when the mixin should be applied in relation to other mixins.
+     * The string is analagous exactly to the strings used to define ordered contributions.
+     * Eg: @Mixin(order={"before:mixina","after:mixinb"}). The ids are mixin names and are case insensitive.
+     * @since 5.2.0.0
+     */
+    String[] order() default {};
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/MixinClasses.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/MixinClasses.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/MixinClasses.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/MixinClasses.java Fri Jul 17 22:53:30 2009
@@ -1,4 +1,4 @@
-// Copyright 2006 The Apache Software Foundation
+// Copyright 2006, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -32,4 +32,15 @@
 public @interface MixinClasses
 {
     Class[] value();
+
+    /**
+     * Specifies the ordering constraints for each mixin specified by value. Order may be a 0-length array signifying
+     * no ordering for any mixin specified by value.  Otherwise, it must be the same length as value.
+     * Each String can specify multiple constraints, separated by ; (before:mixina;after:mixinb).
+     * Alternatively, the entry may be null or the empty string to specify no ordering constraints for that particular
+     * mixin.
+     * @since 5.2.0.0
+     */
+    String[] order() default {};
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixins.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixins.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixins.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/Mixins.java Fri Jul 17 22:53:30 2009
@@ -1,4 +1,4 @@
-// Copyright 2006 The Apache Software Foundation
+// Copyright 2006, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -33,7 +33,11 @@
 {
 
     /**
-     * One or more mixin type names, from which actual mixin class names can be resolved.
+     * One or more mixin type names and optional constraints, from which actual mixin class names can be resolved.
+     * For specifying constraints, do:
+     * mixinname::constraint1;constraint2
+     * For instance:
+     * mixina::before:mixinb;after:mixinc;after:mixind
      */
     String[] value();
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResources.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResources.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResources.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResources.java Fri Jul 17 22:53:30 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -62,4 +62,14 @@
      * @return object used to read and update the parameter
      */
     ParameterAccess getParameterAccess(String parameterName);
+
+    /**
+     * Gets access object suitable for handling mixin fields which are bound to a parameter of the core component
+     * @param boundParameterName the name of the mixin field that should be linked to the core component's field.
+     * @param parentParameterNames the list of parameter names to try in the parent. The first name that matches a
+     *          declared parameter name in the core component will be used. This allows BindParameter to be used with
+     *          mixins that have a similar parameter type with different parameter names (eg:
+     * @since 5.2.0.0
+     */
+    ParameterAccess getContainerBoundParameterAccess(String boundParameterName, String... parentParameterNames);
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResourcesCommon.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResourcesCommon.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResourcesCommon.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/InternalComponentResourcesCommon.java Fri Jul 17 22:53:30 2009
@@ -57,6 +57,19 @@
     Component getMixinByClassName(String mixinClassName);
 
     /**
+     * Returns true if the mixin named by mixinClassName is associated with the core component
+     * @param mixinClassName fully qualified class name
+     * @since 5.2.0.0
+     */
+    boolean isMixingIn(String mixinClassName);
+
+    /**
+     * Registers an action that should be executed immediately after page load.
+     * @since 5.2.0.0
+     */
+    void deferLoadAction(Runnable action);
+
+    /**
      * Constructs a map linking informal parameters to the corresponding bindings.
      *
      * @return map, possible empty

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterAccess.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterAccess.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterAccess.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterAccess.java Fri Jul 17 22:53:30 2009
@@ -1,4 +1,4 @@
-// Copyright 2008 The Apache Software Foundation
+// Copyright 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -77,4 +77,25 @@
      * @see org.apache.tapestry5.Binding#getBindingType()
      */
     Class getBoundType();
+
+    /**
+     * Registers the given ParameterChangeListener. Registering a listener which is already 
+     * registered is silently ignored.
+     * @param listener
+     * @since 5.2.0.0
+     */
+    void registerParameterChangeListener(ParameterChangeListener listener);
+
+    /**
+     * Unregisters the given listener. Unregistering a listener that isn't registered is not an error.
+     * @since 5.2.0.0
+     */
+    void unregisterParameterChangeListener(ParameterChangeListener listener);
+
+    /**
+     * @return true if the parameter value should be cached within a component during rendering
+     * @since 5.2.0.0
+     */
+    boolean shouldCache();
+
 }

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangeListener.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangeListener.java?rev=795264&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangeListener.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangeListener.java Fri Jul 17 22:53:30 2009
@@ -0,0 +1,33 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal;
+
+/**
+ * An object that listens for changes to the underlying values in parameters.
+ * This is currently used to ensure that mixin fields annotated with
+ * {@link org.apache.tapestry5.annotations.BindParameter} and the underlying component variable will stay properly
+ * in sync, regardless of parameter caching.
+ * @since 5.2.0.0
+ */
+public interface ParameterChangeListener
+{
+    /**
+     * Called when the value of a component <code>@Parameter</code> field changes.
+     * Note that this event will be fired regardless of whether the parameter is bound.
+     *
+     * @param event
+     */
+    void parameterChanged(ParameterChangedEvent event);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangedEvent.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangedEvent.java?rev=795264&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangedEvent.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/ParameterChangedEvent.java Fri Jul 17 22:53:30 2009
@@ -0,0 +1,49 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal;
+
+/**
+ * Event associated with a parameter value changing, used by <code>ParameterChangeListener</code>s.
+ * @since 5.2.0.0
+ */
+public class ParameterChangedEvent<T>
+{
+    private final String parameterName;
+    private final T newValue;
+
+
+    public ParameterChangedEvent(String parameterName, T newValue)
+    {
+        this.parameterName = parameterName;
+        this.newValue = newValue;
+    }
+
+    /**
+     * @return the name of the parameter that changed.
+     */
+    public String getParameterName()
+    {
+        return parameterName;
+    }
+
+    /**
+     * @return the new value. May be null.
+     */
+    public T getNewValue()
+    {
+        return newValue;
+    }
+
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java Fri Jul 17 22:53:30 2009
@@ -17,6 +17,7 @@
 import org.apache.tapestry5.*;
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.Orderable;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.Defense;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
@@ -474,4 +475,28 @@
             }
         };
     }
+
+    /**
+     *
+     * @param mixinDef the original mixin definition.
+     * @return an Orderable whose id is the mixin name.
+     */
+    public static Orderable<String> mixinTypeAndOrder(String mixinDef) {
+        int idx = mixinDef.indexOf("::");
+        if (idx == -1)
+        {
+            return new Orderable(mixinDef,mixinDef);
+        }
+        String type = mixinDef.substring(0,idx);
+        String[] constraints = splitMixinConstraints(mixinDef.substring(idx+2));
+
+        return new Orderable(type,type,constraints);
+    }
+
+
+    public static String[] splitMixinConstraints(String s)
+    {
+        return InternalUtils.isBlank(s)?null:s.split(";");
+    }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableComponentModelImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableComponentModelImpl.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableComponentModelImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableComponentModelImpl.java Fri Jul 17 22:53:30 2009
@@ -54,6 +54,8 @@
 
     private List<String> mixinClassNames;
 
+    private Map<String, String[]> mixinOrders;
+
     private boolean informalParametersSupported;
 
     private boolean mixinAfter;
@@ -104,7 +106,7 @@
         return componentClassName;
     }
 
-    public void addParameter(String name, boolean required, boolean allowNull, String defaultBindingPrefix)
+    public void addParameter(String name, boolean required, boolean allowNull, String defaultBindingPrefix, boolean cached)
     {
         Defense.notBlank(name, "name");
         Defense.notBlank(defaultBindingPrefix, "defaultBindingPrefix");
@@ -117,7 +119,16 @@
         if (parameters.containsKey(name))
             throw new IllegalArgumentException(ModelMessages.duplicateParameter(name, componentClassName));
 
-        parameters.put(name, new ParameterModelImpl(name, required, allowNull, defaultBindingPrefix));
+        parameters.put(name, new ParameterModelImpl(name, required, allowNull, defaultBindingPrefix,cached));
+    }
+
+    public void addParameter(String name, boolean required, boolean allowNull, String defaultBindingPrefix)
+    {
+        //assume /false/ for the default because:
+        //if the parameter is actually cached, the only effect will be to reduce that optimization in certain
+        //scenarios (mixin BindParameter).  But if the value is NOT cached but we say it is,
+        //we'll get incorrect behavior.
+        addParameter(name,required,allowNull,defaultBindingPrefix,false);
     }
 
     public ParameterModel getParameterModel(String parameterName)
@@ -220,11 +231,16 @@
         return parentModel == null;
     }
 
-    public void addMixinClassName(String mixinClassName)
+    public void addMixinClassName(String mixinClassName, String... order)
     {
         if (mixinClassNames == null) mixinClassNames = CollectionFactory.newList();
 
         mixinClassNames.add(mixinClassName);
+        if (order != null && order.length > 0)
+        {
+            if (mixinOrders == null) mixinOrders = CollectionFactory.newCaseInsensitiveMap();
+            mixinOrders.put(mixinClassName,order);
+        }
     }
 
     public List<String> getMixinClassNames()
@@ -324,4 +340,9 @@
                ? false
                : parentModel.handlesEvent(eventType);
     }
+
+    public String[] getOrderForMixin(String mixinClassName)
+    {
+        return InternalUtils.get(mixinOrders,mixinClassName);
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableEmbeddedComponentModelImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableEmbeddedComponentModelImpl.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableEmbeddedComponentModelImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/MutableEmbeddedComponentModelImpl.java Fri Jul 17 22:53:30 2009
@@ -46,6 +46,8 @@
      */
     private List<String> mixinClassNames;
 
+    private Map<String,String[]> mixinConstraints;
+
     public MutableEmbeddedComponentModelImpl(String id, String componentType, String componentClassName,
                                              String declaredClass, boolean inheritInformalParameters, Location location)
     {
@@ -106,11 +108,12 @@
         return Collections.unmodifiableList(mixinClassNames);
     }
 
-    public void addMixin(String mixinClassName)
+    public void addMixin(String mixinClassName, String... constraints)
     {
         if (mixinClassNames == null)
         {
             mixinClassNames = CollectionFactory.newList();
+            mixinConstraints = CollectionFactory.newCaseInsensitiveMap();
         }
         else
         {
@@ -119,6 +122,7 @@
         }
 
         mixinClassNames.add(mixinClassName);
+        mixinConstraints.put(mixinClassName,constraints);
     }
 
     public boolean getInheritInformalParameters()
@@ -137,4 +141,9 @@
     {
         return publishedParameters;
     }
+
+    public String[] getConstraintsForMixin(String mixinClassName)
+    {
+        return InternalUtils.get(mixinConstraints,mixinClassName);
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/ParameterModelImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/ParameterModelImpl.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/ParameterModelImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/model/ParameterModelImpl.java Fri Jul 17 22:53:30 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008 The Apache Software Foundation
+// Copyright 2006, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -26,12 +26,20 @@
 
     private final String defaultBindingPrefix;
 
-    public ParameterModelImpl(String name, boolean required, boolean allowNull, String defaultBindingPrefix)
+    private final boolean cached;
+
+    public ParameterModelImpl(
+            String name,
+            boolean required,
+            boolean allowNull,
+            String defaultBindingPrefix,
+            boolean cached)
     {
         this.name = name;
         this.required = required;
         this.allowNull = allowNull;
         this.defaultBindingPrefix = defaultBindingPrefix;
+        this.cached = cached;
     }
 
     public String getName()
@@ -53,4 +61,9 @@
     {
         return allowNull;
     }
+
+    public boolean isCached()
+    {
+        return cached;
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/ComponentAssemblerImpl.java Fri Jul 17 22:53:30 2009
@@ -107,10 +107,11 @@
         }
         catch (RuntimeException ex)
         {
-            throw new RuntimeException(String.format("Exception assembling root component of page %s: %s",
-                                                     pageAssembly.page.getName(),
-                                                     InternalUtils.toMessage(ex)),
-                                       ex);
+            throw new RuntimeException(
+                                PageloadMessages.exceptionAssemblingRootComponent(
+                                        pageAssembly.page.getName(),
+                                        InternalUtils.toMessage(ex)),
+                                ex);
         }
     }
 
@@ -120,7 +121,9 @@
         {
             Instantiator mixinInstantiator = instantiatorSource.getInstantiator(className);
 
-            element.addMixin(InternalUtils.lastTerm(className), mixinInstantiator);
+            ComponentModel model = instantiator.getModel();
+            element.addMixin(InternalUtils.lastTerm(className),
+                    mixinInstantiator,model.getOrderForMixin(className));
         }
     }
 
@@ -159,13 +162,15 @@
         }
         catch (RuntimeException ex)
         {
-            String message = String.format("Exception assembling embedded component '%s' (of type %s, within %s): %s",
-                                           embeddedId,
-                                           instantiator.getModel().getComponentClassName(),
-                                           container.getCompleteId(),
-                                           InternalUtils.toMessage(ex));
-
-            throw new TapestryException(message, location, ex);
+            throw new TapestryException(
+                            PageloadMessages.exceptionAssemblingEmbeddedComponent(
+                                    embeddedId,
+                                    instantiator.getModel().getComponentClassName(),
+                                    container.getCompleteId(),
+                                    InternalUtils.toMessage(ex)
+                            ),
+                            location,
+                            ex);
         }
     }
 
@@ -232,9 +237,7 @@
             String className = getModel().getComponentClassName();
 
             throw new RuntimeException(
-                    String.format(
-                            "Embedded component(s) %s are defined within component class %s (or a super-class of %s), " +
-                                    "but are not present in the component template (%s).",
+                    PageloadMessages.embeddedComponentsNotInTemplate(
                             InternalUtils.joinSorted(embeddedIds.keySet()),
                             className,
                             InternalUtils.lastTerm(className),
@@ -263,6 +266,11 @@
     {
         try
         {
+
+            if (InternalUtils.isBlank(componentClassName))
+            {
+                throw new TapestryException(PageloadMessages.missingComponentType(),location,null);
+            }
             EmbeddedComponentAssemblerImpl embedded = new EmbeddedComponentAssemblerImpl(assemblerSource,
                                                                                          instantiatorSource,
                                                                                          componentClassResolver,
@@ -288,14 +296,12 @@
 
                     if (existingEmbeddedId != null)
                     {
-                        String message = String.format(
-                                "Parameter '%s' of embedded component '%s' can not be published as a parameter of component %s, as it has previously been published by embedded component '%s'.",
-                                publishedParameterName,
-                                embeddedId,
-                                instantiator.getModel().getComponentClassName(),
-                                existingEmbeddedId);
-
-                        throw new TapestryException(message, location, null);
+                        throw new TapestryException(
+                                PageloadMessages.parameterAlreadyPublished(
+                                        publishedParameterName,
+                                        embeddedId,
+                                        instantiator.getModel().getComponentClassName(),
+                                        existingEmbeddedId) , location, null);
                     }
 
                     publishedParameterToEmbeddedId.put(publishedParameterName, embeddedId);
@@ -307,12 +313,14 @@
         }
         catch (Exception ex)
         {
-            String message = String.format("Failure creating embedded component '%s' of %s: %s",
-                                           embeddedId,
-                                           instantiator.getModel().getComponentClassName(),
-                                           InternalUtils.toMessage(ex));
-
-            throw new TapestryException(message, location, ex);
+            throw new TapestryException(
+                    PageloadMessages.failureCreatingEmbeddedComponent(
+                            embeddedId,
+                            instantiator.getModel().getComponentClassName(),
+                            InternalUtils.toMessage(ex)
+                    ),
+                    location,
+                    ex);
         }
     }
 
@@ -354,9 +362,7 @@
 
         if (innerBinder == null)
         {
-            String message = String.format(
-                    "Parameter '%s' of component %s is improperly published from embedded component '%s' (where it does not exist). " +
-                            "This may be a typo in the publishParameters attribute of the @Component annotation.",
+            String message = PageloadMessages.publishedParameterNonexistant(
                     parameterName,
                     instantiator.getModel().getComponentClassName(),
                     embeddedId);

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/CompositeRenderCommand.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/CompositeRenderCommand.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/CompositeRenderCommand.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/CompositeRenderCommand.java Fri Jul 17 22:53:30 2009
@@ -49,8 +49,7 @@
         private void nyi(String methodName)
         {
             throw new IllegalStateException(
-                    String.format("RenderQueue method %s() is not implemented for composited render commands.",
-                                  methodName));
+                    PageloadMessages.compositeRenderCommandMethodNotImplemented(methodName));
         }
     };
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/EmbeddedComponentAssemblerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/EmbeddedComponentAssemblerImpl.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/EmbeddedComponentAssemblerImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/EmbeddedComponentAssemblerImpl.java Fri Jul 17 22:53:30 2009
@@ -15,10 +15,12 @@
 package org.apache.tapestry5.internal.pageload;
 
 import org.apache.tapestry5.internal.TapestryInternalUtils;
+
 import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
 import org.apache.tapestry5.internal.services.Instantiator;
 import org.apache.tapestry5.internal.structure.ComponentPageElement;
 import org.apache.tapestry5.ioc.Location;
+import org.apache.tapestry5.ioc.Orderable;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.internal.util.TapestryException;
@@ -42,6 +44,7 @@
     private final Location location;
 
     private final Map<String, Instantiator> mixinIdToInstantiator = CollectionFactory.newCaseInsensitiveMap();
+    private final Map<String, String[]> mixinsIdToOrderConstraints = CollectionFactory.newCaseInsensitiveMap();
 
     /**
      * Maps parameter names (both simple, and qualified with the mixin id) to the corresponding QualifiedParameterName.
@@ -85,7 +88,7 @@
 
         for (String className : componentModel.getMixinClassNames())
         {
-            addMixin(className);
+            addMixin(className,componentModel.getOrderForMixin(className));
         }
 
         // If there's an embedded model (i.e., there was an @Component annotation)
@@ -95,17 +98,18 @@
         {
             for (String className : embeddedModel.getMixinClassNames())
             {
-                addMixin(className);
+                addMixin(className,embeddedModel.getConstraintsForMixin(className));
             }
         }
 
         // And the template may include a t:mixins element to define yet more mixin.
-
-        for (String mixinType : TapestryInternalUtils.splitAtCommas(templateMixins))
+        // Template strings specified as:
+        for (String mixinDef : TapestryInternalUtils.splitAtCommas(templateMixins))
         {
-            String className = componentClassResolver.resolveMixinTypeToClassName(mixinType);
+            Orderable<String> order = TapestryInternalUtils.mixinTypeAndOrder(mixinDef);
+            String className = componentClassResolver.resolveMixinTypeToClassName(order.getId());
 
-            addMixin(className);
+            addMixin(className,order.getConstraints());
         }
 
         informalParametersMixinId = prescanMixins();
@@ -152,7 +156,7 @@
         }
     }
 
-    private void addMixin(String className)
+    private void addMixin(String className, String... order)
     {
         Instantiator mixinInstantiator = instantiatorSource.getInstantiator(className);
 
@@ -160,12 +164,12 @@
 
         if (mixinIdToInstantiator.containsKey(mixinId))
             throw new TapestryException(
-                    String.format("Mixins applied to a component must be unique. Mixin '%s' has already been applied.",
-                                  mixinId),
+                    PageloadMessages.uniqueMixinRequired(mixinId),
                     location, null);
 
 
         mixinIdToInstantiator.put(mixinId, mixinInstantiator);
+        mixinsIdToOrderConstraints.put(mixinId, order);
     }
 
     private ComponentModel getModel(String className)
@@ -186,11 +190,10 @@
             String mixinId = parameterName.substring(0, dotx);
             if (!mixinIdToInstantiator.containsKey(mixinId))
             {
-                String message = String.format("Mixin id for parameter '%s' not found. Attached mixins: %s.",
-                                               parameterName,
-                                               InternalUtils.joinSorted(mixinIdToInstantiator.keySet()));
-
-                throw new TapestryException(message, location, null);
+                throw new TapestryException(
+                        PageloadMessages.mixinidForParamnotfound(parameterName, mixinIdToInstantiator.keySet()),
+                        location, 
+                        null);
             }
         }
         else
@@ -244,7 +247,7 @@
             String mixinId = entry.getKey();
             Instantiator instantiator = entry.getValue();
 
-            newElement.addMixin(mixinId, instantiator);
+            newElement.addMixin(mixinId, instantiator, mixinsIdToOrderConstraints.get(mixinId));
         }
     }
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageAssembly.java Fri Jul 17 22:53:30 2009
@@ -82,11 +82,10 @@
 
             if (e.getComponentResources().getComponentModel().getComponentClassName().equals(componentClassName))
             {
-                String message = String.format(
-                        "The template for component %s is recursive (contains another direct or indirect reference to component %<s). This is not supported (components may not contain themselves).",
-                        componentClassName);
-
-                throw new TapestryException(message, location, null);
+                throw new TapestryException(
+                        PageloadMessages.recursiveTemplate(componentClassName),
+                        location, 
+                        null);
             }
         }
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java Fri Jul 17 22:53:30 2009
@@ -305,13 +305,13 @@
                                                       Stack<TemplateToken> queue,
                                                       List<ComponentTemplate> overrideSearch)
     {
-        String extentionPointId = extensionPointToken.getExtentionPointId();
+        String extensionPointId = extensionPointToken.getExtensionPointId();
 
         // Work up from the component, through its base classes, towards the last non-extension template.
 
         for (ComponentTemplate t : overrideSearch)
         {
-            List<TemplateToken> tokens = t.getExtensionPointTokens(extentionPointId);
+            List<TemplateToken> tokens = t.getExtensionPointTokens(extensionPointId);
 
             if (tokens != null)
             {
@@ -324,7 +324,7 @@
         // not find an override, somewhere, for it.
 
         throw new TapestryException(
-                String.format("Could not find an override for extension point '%s'.", extentionPointId),
+                PageloadMessages.couldNotFindOverride(extensionPointId),
                 extensionPointToken.getLocation(), null);
     }
 
@@ -343,9 +343,7 @@
 
             if (parentModel == null)
             {
-                throw new RuntimeException(String.format(
-                        "Component %s uses an extension template, but does not have a parent component.",
-                        model.getComponentClassName()));
+                throw new RuntimeException(PageloadMessages.noParentForExtension(model));
             }
 
             ComponentTemplate parentTemplate = templateSource.getTemplate(parentModel, assembler.getLocale());
@@ -433,7 +431,7 @@
                 break;
 
             default:
-                throw new IllegalStateException("Not yet implemented: " + context.peekType());
+                throw new IllegalStateException(PageloadMessages.tokenNotImplemented(context.peekType()));
         }
     }
 
@@ -528,12 +526,10 @@
 
                 if (binder == null)
                 {
-                    String message = String.format(
-                            "Component %s does not include a formal parameter '%s' (and does not support informal parameters).",
-                            element.getCompleteId(),
-                            parameterName);
-
-                    throw new TapestryException(message, token.getLocation(), null);
+                    throw new TapestryException(
+                            PageloadMessages.parameterNotSupported(element.getCompleteId(), parameterName), 
+                            token.getLocation(),
+                            null);
                 }
 
                 binder.bind(pageAssembly.createdElement.peek(), binding);
@@ -706,15 +702,10 @@
 
             if (InternalUtils.isNonBlank(modelType) && embeddedType != null)
             {
-                String message = String.format(
-                        "Embedded component '%s' provides a type attribute in the template ('%s') " +
-                                "as well as in the component class ('%s'). " +
-                                "You should not provide a type attribute in the template when defining an embedded component " +
-                                "within the component class.",
-                        embeddedId, embeddedType, modelType
-                );
-
-                throw new TapestryException(message, token, null);
+                throw new TapestryException(
+                        PageloadMessages.redundantEmbeddedComponentTypes(embeddedId,embeddedType,modelType),
+                        token,
+                        null);
             }
 
             embeddedType = modelType;
@@ -882,7 +873,7 @@
      * Adds a deferred action to the PageAssembly, to handle connecting the embedded components' parameter to the
      * container component's parameter once everything else has been built.
      *
-     * @param assembler
+     * @param context
      * @param parameterName
      * @param containerParameterName
      */

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageloadMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageloadMessages.java?rev=795264&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageloadMessages.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageloadMessages.java Fri Jul 17 22:53:30 2009
@@ -0,0 +1,147 @@
+// Copyright 2009 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.pageload;
+
+import org.apache.tapestry5.ioc.Messages;
+import org.apache.tapestry5.ioc.Resource;
+import org.apache.tapestry5.ioc.internal.util.MessagesImpl;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.internal.parser.TokenType;
+import org.apache.tapestry5.model.ComponentModel;
+
+import java.util.Collection;
+
+/**
+ * Localized exception message support for pageload-related classes.
+ * @since 5.2.0.0
+ */
+final class PageloadMessages
+{
+
+    private static final Messages MESSAGES = MessagesImpl.forClass(PageloadMessages.class);
+
+    private PageloadMessages()
+    {
+    }
+
+    public static String uniqueMixinRequired(String mixinId)
+    {
+        return MESSAGES.format("unique_mixin_required", mixinId);
+    }
+
+    public static String mixinidForParamnotfound(String parameterName, Collection<String> availableMixins)
+    {
+        return MESSAGES.format("mixinid_for_paramnotfound", parameterName, InternalUtils.joinSorted(availableMixins));
+    }
+
+    public static String missingComponentType()
+    {
+        return MESSAGES.get("missing_component_type");
+    }
+
+    public static String noMoreTokens()
+    {
+        return MESSAGES.get("no_more_tokens");
+    }
+
+    public static String couldNotFindOverride(String extensionPointId)
+    {
+        return MESSAGES.format("could_not_find_override", extensionPointId);
+    }
+
+    public static String noParentForExtension(ComponentModel model)
+    {
+        return MESSAGES.format("no_parent_for_extension", model.getComponentClassName());
+    }
+
+    public static String tokenNotImplemented(TokenType type)
+    {
+        return MESSAGES.format("token_not_implemented", type.toString());
+    }
+
+    public static String parameterNotSupported(String completeId, String parameterName)
+    {
+        return MESSAGES.format("parameter_not_supported", completeId, parameterName);
+    }
+
+    public static String redundantEmbeddedComponentTypes(String embeddedId, String embeddedType, String modelType)
+    {
+        return MESSAGES.format("redundant_embedded_component_types", embeddedId, embeddedType, modelType);
+    }
+
+    public static String recursiveTemplate(String componentClassName)
+    {
+        return MESSAGES.format("recursive_template", componentClassName);
+    }
+
+    public static String compositeRenderCommandMethodNotImplemented(String methodName)
+    {
+        return MESSAGES.format("composite_render_command_method_not_implemented",methodName);
+    }
+
+    public static String exceptionAssemblingRootComponent(String pageName, String exceptionMessage)
+    {
+        return MESSAGES.format("exception_assembling_root_component",pageName,exceptionMessage);
+    }
+
+    public static String exceptionAssemblingEmbeddedComponent(
+            String embeddedId,
+            String embeddedClassName,
+            String containerId,
+            String exception)
+    {
+        return MESSAGES.format("exception_assembling_embedded_component",
+                embeddedId,
+                embeddedClassName,
+                containerId,
+                exception);
+    }
+
+    public static String embeddedComponentsNotInTemplate(
+            String joinedComponentIds,
+            String qualifiedClassName,
+            String simpleClassName,
+            Resource templateResource)
+    {
+        return MESSAGES.format("embedded_components_not_in_template",
+                joinedComponentIds,
+                qualifiedClassName,
+                simpleClassName,
+                templateResource);
+    }
+
+    public static String parameterAlreadyPublished(
+            String publishedParameterName, 
+            String embeddedId,
+            String componentClassName,
+            String existingEmbeddedId)
+    {
+        return MESSAGES.format("parameter_already_published",
+                publishedParameterName,
+                embeddedId,
+                componentClassName,
+                existingEmbeddedId);
+    }
+
+    public static String failureCreatingEmbeddedComponent(String embeddedId, String containerClass, String exception)
+    {
+        return MESSAGES.format("failure_creating_embedded_component",embeddedId, containerClass, exception);
+    }
+
+    public static String publishedParameterNonexistant(String parameterName, String publishingClass, String embeddedId)
+    {
+        return MESSAGES.format("published_parameter_nonexistant", parameterName, publishingClass, embeddedId);
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStreamImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStreamImpl.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStreamImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/TokenStreamImpl.java Fri Jul 17 22:53:30 2009
@@ -45,7 +45,7 @@
     private void checkMore()
     {
         if (!more())
-            throw new IllegalStateException("No more template tokens.");
+            throw new IllegalStateException(PageloadMessages.noMoreTokens());
     }
 
     public <T extends TemplateToken> T next(Class<T> type)

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/parser/ExtensionPointToken.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/parser/ExtensionPointToken.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/parser/ExtensionPointToken.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/parser/ExtensionPointToken.java Fri Jul 17 22:53:30 2009
@@ -25,16 +25,16 @@
  */
 public class ExtensionPointToken extends TemplateToken
 {
-    private final String extentionPointId;
+    private final String extensionPointId;
 
-    public ExtensionPointToken(String extentionPointId, Location location)
+    public ExtensionPointToken(String extensionPointId, Location location)
     {
         super(TokenType.EXTENSION_POINT, location);
-        this.extentionPointId = extentionPointId;
+        this.extensionPointId = extensionPointId;
     }
 
-    public String getExtentionPointId()
+    public String getExtensionPointId()
     {
-        return extentionPointId;
+        return extensionPointId;
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StaxTemplateParser.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StaxTemplateParser.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StaxTemplateParser.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StaxTemplateParser.java Fri Jul 17 22:53:30 2009
@@ -303,10 +303,7 @@
     {
         int lineNumber = reader.getLocation().getLineNumber();
 
-        if (cachedLocation != null && cachedLocation.getLine() != lineNumber)
-            cachedLocation = null;
-
-        if (cachedLocation == null)
+        if (cachedLocation == null || cachedLocation.getLine() != lineNumber)
             cachedLocation = new LocationImpl(resource, lineNumber);
 
         return cachedLocation;

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElement.java Fri Jul 17 22:53:30 2009
@@ -72,8 +72,9 @@
      *
      * @param mixinId      a unique id for the mixin, the last term of the mixin's class name
      * @param instantiator used to instantiate an instance of the mixin
+     * @param order        Ordering strings used to determine the order of mixin execution.
      */
-    void addMixin(String mixinId, Instantiator instantiator);
+    void addMixin(String mixinId, Instantiator instantiator, String... order);
 
     /**
      * @param mixinId       id of previously added mixin

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/ComponentPageElementImpl.java Fri Jul 17 22:53:30 2009
@@ -24,11 +24,8 @@
 import org.apache.tapestry5.internal.util.NotificationEventCallback;
 import org.apache.tapestry5.ioc.BaseLocatable;
 import org.apache.tapestry5.ioc.Location;
-import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.ioc.internal.util.Defense;
 import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.ioc.internal.util.TapestryException;
+import org.apache.tapestry5.ioc.internal.util.*;
 import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.model.ParameterModel;
 import org.apache.tapestry5.runtime.Component;
@@ -474,6 +471,12 @@
 
     private final String id;
 
+    private Orderer<Component> mixinBeforeOrderer;
+
+    private Orderer<Component> mixinAfterOrderer;
+
+    private List<Runnable> deferredLoadActions;
+
     private boolean loaded;
 
     /**
@@ -667,7 +670,7 @@
         children.put(childId, child);
     }
 
-    public void addMixin(String mixinId, Instantiator instantiator)
+    public void addMixin(String mixinId, Instantiator instantiator, String... order)
     {
         if (mixinIdToComponentResources == null)
         {
@@ -684,8 +687,19 @@
                                                                                       instantiator);
 
         mixinIdToComponentResources.put(mixinId, resources);
-
-        components.add(resources.getComponent());
+        //note that since we're using explicit ordering now,
+        //we don't add anything to components until we page load; instead, we add
+        //to the orderers.
+        if (order == null) order = new String[0];
+        if (resources.getComponentModel().isMixinAfter())
+        {
+            if (mixinAfterOrderer == null) mixinAfterOrderer = new Orderer<Component>(getLogger());
+            mixinAfterOrderer.add(mixinId,resources.getComponent(),order);
+        } else
+        {
+            if (mixinBeforeOrderer == null) mixinBeforeOrderer =new Orderer<Component>(getLogger());
+            mixinBeforeOrderer.add(mixinId,resources.getComponent(),order);
+        }
     }
 
     public void bindMixinParameter(String mixinId, String parameterName, Binding binding)
@@ -753,37 +767,26 @@
 
     public void containingPageDidLoad()
     {
-        // If this component has mixins, add the core component to the end of the list, after the
+        // If this component has mixins, order them according to:
         // mixins.
 
         if (components != null)
         {
             List<Component> ordered = CollectionFactory.newList();
 
-            Iterator<Component> i = components.iterator();
-
-            // Add all the normal components to the final list.
-
-            while (i.hasNext())
-            {
-                Component mixin = i.next();
-
-                if (mixin.getComponentResources().getComponentModel().isMixinAfter()) continue;
-
-                ordered.add(mixin);
-
-                // Remove from list, leaving just the late executing mixins
-
-                i.remove();
-            }
+            if (mixinBeforeOrderer != null)
+                ordered.addAll(mixinBeforeOrderer.getOrdered());
 
             ordered.add(coreComponent);
 
             // Add the remaining, late executing mixins
-
-            ordered.addAll(components);
+            if (mixinAfterOrderer != null)
+                ordered.addAll(mixinAfterOrderer.getOrdered());
 
             components = ordered;
+            //no need to keep the orderers around.
+            mixinBeforeOrderer = null;
+            mixinAfterOrderer = null;
         }
 
         loaded = true;
@@ -792,10 +795,19 @@
         // that is invoked first, before we check for unbound parameters.
 
         invoke(false, CONTAINING_PAGE_DID_LOAD);
-
+        executeDeferredLoadActions();
         verifyRequiredParametersAreBound();
     }
 
+    private void executeDeferredLoadActions()
+    {
+        if (deferredLoadActions == null) return;
+        for(Runnable action : deferredLoadActions)
+        {
+            action.run();
+        }
+        deferredLoadActions = null;//having executed them, we have no need now to store them.
+    }
 
     public void enqueueBeforeRenderBody(RenderQueue queue)
     {
@@ -857,24 +869,38 @@
 
     public Component getMixinByClassName(String mixinClassName)
     {
-        Component result = null;
+        Component result = mixinForClassName(mixinClassName);
 
-        if (mixinIdToComponentResources != null)
+
+        if (result == null) throw new TapestryException(StructureMessages.unknownMixin(completeId, mixinClassName),
+                                                        getLocation(), null);
+
+        return result;
+    }
+
+    private Component mixinForClassName(String mixinClassName)
+    {
+
+        if (mixinIdToComponentResources == null) return null;
+        for (InternalComponentResources resources : mixinIdToComponentResources.values())
         {
-            for (InternalComponentResources resources : mixinIdToComponentResources.values())
+            if (resources.getComponentModel().getComponentClassName().equals(mixinClassName))
             {
-                if (resources.getComponentModel().getComponentClassName().equals(mixinClassName))
-                {
-                    result = resources.getComponent();
-                    break;
-                }
+                return resources.getComponent();
             }
         }
+        return null;
+    }
 
-        if (result == null) throw new TapestryException(StructureMessages.unknownMixin(completeId, mixinClassName),
-                                                        getLocation(), null);
+    public boolean isMixingIn(String mixinClassName)
+    {
+        return mixinForClassName(mixinClassName) != null;
+    }
 
-        return result;
+    public void deferLoadAction(Runnable action)
+    {
+        if (deferredLoadActions == null) deferredLoadActions = CollectionFactory.newList();
+        deferredLoadActions.add(action);
     }
 
     public ComponentResources getMixinResources(String mixinId)

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/InternalComponentResourcesImpl.java Fri Jul 17 22:53:30 2009
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,8 +15,7 @@
 package org.apache.tapestry5.internal.structure;
 
 import org.apache.tapestry5.*;
-import org.apache.tapestry5.internal.InternalComponentResources;
-import org.apache.tapestry5.internal.ParameterAccess;
+import org.apache.tapestry5.internal.*;
 import org.apache.tapestry5.internal.services.Instantiator;
 import org.apache.tapestry5.ioc.AnnotationProvider;
 import org.apache.tapestry5.ioc.Location;
@@ -37,6 +36,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * The bridge between a component and its {@link ComponentPageElement}, that supplies all kinds of resources to the
@@ -66,6 +66,10 @@
     // Case insensitive map from parameter name to ParameterAccess
     private Map<String, ParameterAccess> access;
 
+    // Case-insensitive map from container-parameter name to ParameterAccess, for BindParameter.
+    // Should only ever be used for mixins.
+    private Map<String, ParameterAccess> containerParameterAccess;
+
     private Messages messages;
 
     // Case insensitive
@@ -324,6 +328,16 @@
         return element.getMixinByClassName(mixinClassName);
     }
 
+    public boolean isMixingIn(String mixinClassName)
+    {
+        return element.isMixingIn(mixinClassName);
+    }
+
+    public void deferLoadAction(Runnable action)
+    {
+        element.deferLoadAction(action);
+    }
+
     public void renderInformalParameters(MarkupWriter writer)
     {
         if (bindings == null) return;
@@ -474,6 +488,50 @@
         return result;
     }
 
+    public ParameterAccess getContainerBoundParameterAccess(final String boundParameterName, String... parentParameterNames)
+    {
+        if (containerParameterAccess == null) containerParameterAccess = CollectionFactory.newCaseInsensitiveMap();
+
+        ParameterAccess result = containerParameterAccess.get(boundParameterName);
+        if (result == null)
+        {
+            final InternalComponentResources res = (InternalComponentResources) getContainerResources();
+            //Ideally, this check would occur at class fabrication time. But there's not currently a way
+            //to tell if a component class is a mixin class, short of checking for "mixins" in the FQCN.
+            //So we check to make sure that this component class name is in the set of mixins defined for the container
+            //resources.
+            if (!res.isMixingIn(this.getComponentModel().getComponentClassName())) {
+                //then we're not a mixin, we're a component in the tree.
+                throw new TapestryException(StructureMessages.bindParameterOnlyOnMixin(boundParameterName, this),this,null);
+            }
+            //Have to be careful here. Problem is that if the mixin is not @MixinAfter, its PAGE_DID_LOAD will be called
+            //before the core component's. That can potentially result in missing default bindings if we
+            //call getParameterAcces at the wrong time (the unbound parameter access will be cached...).
+            String parentParameterName = findParentParameterName(parentParameterNames);
+            if (parentParameterName == null)
+            {
+                throw new TapestryException(
+                        StructureMessages.noSuchCoreComponentParameter(this,boundParameterName,parentParameterNames), 
+                        this,null);
+            }
+            result = createContainerParameterAccess(parentParameterName);
+            containerParameterAccess.put(boundParameterName,result);
+        }
+        return result;
+    }
+
+    private String findParentParameterName(String... queries)
+    {
+        for(String query : queries)
+        {
+            if(getContainerResources().getComponentModel().getParameterModel(query) != null)
+            {
+                return query;
+            }
+        }
+        return null;
+    }
+
     private ParameterAccess createParameterAccess(final String parameterName)
     {
         final Binding binding = getBinding(parameterName);
@@ -482,8 +540,11 @@
 
         final boolean allowNull = parameterModel == null ? true : parameterModel.isAllowNull();
 
+        final boolean cache = parameterModel == null ? false : parameterModel.isCached();
+
         return new ParameterAccess()
         {
+
             public boolean isBound()
             {
                 return binding != null;
@@ -529,15 +590,24 @@
 
             public <T> void write(T parameterValue)
             {
-                if (binding == null) return;
 
-                Class bindingType = binding.getBindingType();
+                if (binding == null)
+                {
+                    //have to fire in case there's a mixin watching value;
+                    //even if it's not bound to any other value,
+                    //the mixin needs to know that the value internal to the component
+                    //was changed.
+                    fireParameterChanged(parameterName, parameterValue);
+                    return;
+                }
 
+                Class bindingType = binding.getBindingType();
                 try
                 {
                     Object coerced = elementResources.coerce(parameterValue, bindingType);
 
                     binding.set(coerced);
+                    fireParameterChanged(parameterName,coerced);
                 }
                 catch (Exception ex)
                 {
@@ -561,6 +631,113 @@
             {
                 return binding == null ? null : binding.getAnnotation(annotationClass);
             }
+
+            private Set<ParameterChangeListener> listeners;
+
+            public void registerParameterChangeListener(ParameterChangeListener listener)
+            {
+                Defense.notNull(listener,"listener");
+                if (listeners == null) listeners = CollectionFactory.newSet();
+                listeners.add(listener);
+            }
+
+            public void unregisterParameterChangeListener(ParameterChangeListener listener)
+            {
+                if (listeners == null) return;
+                listeners.remove(listener);
+            }
+
+            public boolean shouldCache()
+            {
+                return cache;
+            }
+
+            protected void fireParameterChanged(String parameterName, Object newValue)
+            {
+                ParameterChangedEvent event = new ParameterChangedEvent(parameterName,newValue);
+                for(ParameterChangeListener l : listeners)
+                {
+                    l.parameterChanged(event);
+                }
+            }
+
         };
     }
+
+    private ParameterAccess createContainerParameterAccess(final String parentParameterName)
+    {
+
+        return new ParameterAccess()
+        {
+
+            private ParameterAccess access()
+            {
+                return element.getComponentResources().getParameterAccess(parentParameterName);
+            }
+
+            public boolean isBound()
+            {
+                return element.getBinding(parentParameterName) != null;
+            }
+
+            public Object read(String desiredTypeName)
+            {
+                return access().read(desiredTypeName);
+            }
+
+            public <T> T read(Class<T> expectedType)
+            {
+                return access().read(expectedType);
+            }
+
+            public <T> void write(T parameterValue)
+            {
+                access().write(parameterValue);
+            }
+
+            public boolean isInvariant()
+            {
+                return access().isInvariant();
+            }
+
+            public Class getBoundType()
+            {
+                return access().getBoundType();
+            }
+
+            public void registerParameterChangeListener(final ParameterChangeListener listener)
+            {
+                //if it's not bound, try defering.
+                if (isBound())
+                {
+                    access().registerParameterChangeListener(listener);
+                } else
+                {
+                    //try waiting for it. If it's not bound after load, then it's not bound at all.
+                    element.deferLoadAction(new Runnable() {
+                        public void run()
+                        {
+                            access().registerParameterChangeListener(listener);
+                        }
+                    });
+                }
+            }
+
+            public void unregisterParameterChangeListener(ParameterChangeListener listener)
+            {
+                access().unregisterParameterChangeListener(listener);
+            }
+
+            public boolean shouldCache()
+            {
+                return access().shouldCache();
+            }
+
+            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+            {
+                return access().getAnnotation(annotationClass);
+            }
+        };
+
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java?rev=795264&r1=795263&r2=795264&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/structure/StructureMessages.java Fri Jul 17 22:53:30 2009
@@ -18,10 +18,12 @@
 import org.apache.tapestry5.ioc.Messages;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.ioc.internal.util.MessagesImpl;
+import org.apache.tapestry5.ComponentResources;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
+import java.util.Arrays;
 
 final class StructureMessages
 {
@@ -119,4 +121,21 @@
     {
         return MESSAGES.get("persist-change-before-load-complete");
     }
+
+    static String noSuchCoreComponentParameter(ComponentResources mixinResources,
+                                               String boundId, String...parameters)
+    {
+        return MESSAGES.format("no-such-core-component-parameter",
+                boundId,
+                mixinResources.getComponentModel().getComponentClassName(),
+                mixinResources.getContainerResources().getComponentModel().getComponentClassName(),
+                InternalUtils.joinSorted(Arrays.asList(parameters)),
+                InternalUtils.joinSorted(Arrays.asList(mixinResources.getContainerResources().getComponentModel().getDeclaredParameterNames())));
+    }
+
+    public static String bindParameterOnlyOnMixin(String boundParameterName, InternalComponentResourcesImpl internalComponentResources)
+    {
+        return MESSAGES.format("bind-parameter-only-on-mixin",boundParameterName,
+                internalComponentResources.getComponentModel().getComponentClassName());
+    }
 }