You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by uk...@apache.org on 2014/06/26 20:44:06 UTC

git commit: TAP5-2138: Support multiple @PageActivationContext

Repository: tapestry-5
Updated Branches:
  refs/heads/master 5a8ac6883 -> b02c35148


TAP5-2138: Support multiple @PageActivationContext


Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/b02c3514
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/b02c3514
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/b02c3514

Branch: refs/heads/master
Commit: b02c35148bb74eca48b96bb768982336815a9635
Parents: 5a8ac68
Author: uklance <uk...@gmail.com>
Authored: Thu Jun 26 19:42:29 2014 +0100
Committer: uklance <uk...@gmail.com>
Committed: Thu Jun 26 19:42:49 2014 +0100

----------------------------------------------------------------------
 .../annotations/PageActivationContext.java      |   9 +-
 .../transform/PageActivationContextWorker.java  | 145 ++++++++++++++-----
 .../src/test/app1/PACMultipleAnnotationDemo.tml |  16 ++
 .../integration/app1/CoreBehaviorsTests.java    |  38 +++++
 .../tapestry5/integration/app1/pages/Index.java |   3 +
 .../app1/pages/PACMultipleAnnotationDemo.java   |  51 +++++++
 6 files changed, 222 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b02c3514/tapestry-core/src/main/java/org/apache/tapestry5/annotations/PageActivationContext.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/annotations/PageActivationContext.java b/tapestry-core/src/main/java/org/apache/tapestry5/annotations/PageActivationContext.java
index 9d70f5c..ae1d129 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/annotations/PageActivationContext.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/annotations/PageActivationContext.java
@@ -28,7 +28,8 @@ import org.apache.tapestry5.ioc.annotations.UseWith;
  * In order to use this annotation you must contribute a {@link org.apache.tapestry5.ValueEncoder} for the class of the
  * annotated property.
  * <p/>
- * You should not use this annotation more than once per page class; doing it will result in a runtime exception.
+ * If using this annotation more than once per page class you must specify unique indexes for each. Indexes must start
+ * at 0 and increment by 1 (eg. if 3 annotations are present they must have indexes of 0, 1 and 2)
  */
 @Target(FIELD)
 @Documented
@@ -45,4 +46,10 @@ public @interface PageActivationContext
      * Whether to create a passivate event handler
      */
     boolean passivate() default true;
+    
+    /**
+     * The index of the page activation context parameter (default 0)
+     * @since 5.4
+     */
+    int index() default 0;
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b02c3514/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PageActivationContextWorker.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PageActivationContextWorker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PageActivationContextWorker.java
index 03a2cad..c01dab2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PageActivationContextWorker.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/PageActivationContextWorker.java
@@ -14,6 +14,12 @@
 
 package org.apache.tapestry5.internal.transform;
 
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
 import org.apache.tapestry5.EventConstants;
 import org.apache.tapestry5.annotations.PageActivationContext;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
@@ -28,8 +34,6 @@ import org.apache.tapestry5.services.ComponentEventHandler;
 import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
 import org.apache.tapestry5.services.transform.TransformationSupport;
 
-import java.util.List;
-
 /**
  * Provides the page activation context handlers.
  *
@@ -37,80 +41,143 @@ import java.util.List;
  */
 public class PageActivationContextWorker implements ComponentClassTransformWorker2
 {
+    private static final Comparator<PlasticField> INDEX_COMPARATOR = new Comparator<PlasticField>()
+    {
+        public int compare(PlasticField field1, PlasticField field2) {
+            int index1 = field1.getAnnotation(PageActivationContext.class).index();
+            int index2 = field2.getAnnotation(PageActivationContext.class).index();
+
+            int compare = index1 < index2 ? -1 : (index1 > index2 ? 1 : 0);
+            if (compare == 0)
+            {
+                compare = field1.getName().compareTo(field2.getName());
+            }
+            return compare;
+        }
+    };
+
     public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model)
     {
         List<PlasticField> fields = plasticClass.getFieldsWithAnnotation(PageActivationContext.class);
 
-        switch (fields.size())
+        if (!fields.isEmpty())
         {
-            case 0:
-                break;
-
-            case 1:
-
-                transformField(support, fields.get(0));
-
-                break;
-
-            default:
-
-                List<String> names = CollectionFactory.newList();
-
-                for (PlasticField field : fields)
-                {
-                    names.add(field.getName());
-                }
-
-                throw new RuntimeException(String.format("Illegal number of fields annotated with @PageActivationContext: %s. Only one field is allowed.", InternalUtils.joinSorted(names)));
+            transformFields(support, fields);
         }
     }
 
-    private void transformField(TransformationSupport support, PlasticField field)
+    private void transformFields(TransformationSupport support, List<PlasticField> fields)
     {
-        PageActivationContext annotation = field.getAnnotation(PageActivationContext.class);
-
-        FieldHandle handle = field.getHandle();
+        List<PlasticField> sortedFields = CollectionFactory.newList(fields);
+        Collections.sort(sortedFields, INDEX_COMPARATOR);
+        validateSortedFields(sortedFields);
+
+        PlasticField firstField = sortedFields.get(0);
+        PageActivationContext firstAnnotation = firstField.getAnnotation(PageActivationContext.class);
+
+        // these arrays reduce memory usage and allow the PlasticField instances to be garbage collected
+        FieldHandle[] handles = new FieldHandle[sortedFields.size()];
+        String[] typeNames = new String[sortedFields.size()];
+
+        int i = 0;
+        for (PlasticField field : sortedFields) {
+            handles[i] = field.getHandle();
+            typeNames[i] = field.getTypeName();
+            ++i;
+        }
 
-        if (annotation.activate())
+        if (firstAnnotation.activate())
         {
             support.addEventHandler(EventConstants.ACTIVATE, 1,
-                    "PageActivationContextWorker activate event handler",
-                    createActivationHandler(field.getTypeName(), handle));
+                    "PageActivationContextWorker activate event handler", createActivationHandler(handles, typeNames));
         }
 
-        if (annotation.passivate())
+        if (firstAnnotation.passivate())
         {
             support.addEventHandler(EventConstants.PASSIVATE, 0,
-                    "PageActivationContextWorker passivate event handler", createPassivateHandler(handle));
+                    "PageActivationContextWorker passivate event handler", createPassivateHandler(handles));
         }
 
         // We don't claim the field, and other workers may even replace it with a FieldConduit.
+    }
+
+    private void validateSortedFields(List<PlasticField> sortedFields) {
+        List<Integer> expectedIndexes = CollectionFactory.newList();
+        List<Integer> actualIndexes = CollectionFactory.newList();
+        Set<Boolean> activates = CollectionFactory.newSet();
+        Set<Boolean> passivates = CollectionFactory.newSet();
+
+        for (int i = 0; i < sortedFields.size(); ++i) {
+            PlasticField field = sortedFields.get(i);
+            PageActivationContext annotation = field.getAnnotation(PageActivationContext.class);
+            expectedIndexes.add(i);
+            actualIndexes.add(annotation.index());
+            activates.add(annotation.activate());
+            passivates.add(annotation.passivate());
+        }
 
+        List<String> errors = CollectionFactory.newList(); 
+        if (!expectedIndexes.equals(actualIndexes)) {
+            errors.add(String.format("Index values must start at 0 and increment by 1 (expected [%s], found [%s])", 
+                    InternalUtils.join(expectedIndexes), InternalUtils.join(actualIndexes)));
+        }
+        if (activates.size() > 1) {
+            errors.add("Illegal values for 'activate' (all fields must have the same value)");
+        }
+        if (passivates.size() > 1) {
+            errors.add("Illegal values for 'passivate' (all fields must have the same value)");
+        }
+        if (!errors.isEmpty()) {
+            throw new RuntimeException(String.format("Invalid values for @PageActivationContext: %s", InternalUtils.join(errors)));
+        }
     }
 
-    private static ComponentEventHandler createActivationHandler(final String fieldType, final FieldHandle handle)
+    private static ComponentEventHandler createActivationHandler(final FieldHandle[] handles, final String[] fieldTypes)
     {
         return new ComponentEventHandler()
         {
             public void handleEvent(Component instance, ComponentEvent event)
             {
-                Object value = event.coerceContext(0, fieldType);
-
-                handle.set(instance, value);
+                int count = Math.min(handles.length, event.getEventContext().getCount());
+                for (int i = 0; i < count; ++i)
+                {
+                    String fieldType = fieldTypes[i];
+                    FieldHandle handle = handles[i];
+                    Object value = event.coerceContext(i, fieldType);
+                    handle.set(instance, value);
+                }
             }
         };
     }
 
-    private static ComponentEventHandler createPassivateHandler(final FieldHandle handle)
+    private static ComponentEventHandler createPassivateHandler(final FieldHandle[] handles)
     {
         return new ComponentEventHandler()
         {
             public void handleEvent(Component instance, ComponentEvent event)
             {
-                Object value = handle.get(instance);
+                Object result;
+                if (handles.length == 1) {
+                    // simple / common case for a single @PageActivationContext
+                    result = handles[0].get(instance);
+                } else {
+                    LinkedList<Object> list = CollectionFactory.newLinkedList();
+
+                    // iterate backwards
+                    for (int i = handles.length - 1; i > -1; i--) {
+                        FieldHandle handle = handles[i];
+                        Object value = handle.get(instance);
+
+                        // ignore trailing nulls
+                        if (value != null || !list.isEmpty()) {
+                            list.addFirst(value);
+                        }
+                    }
+                    result = list.isEmpty() ? null : list;
+                }
 
-                event.storeResult(value);
+                event.storeResult(result);
             }
         };
     }
-}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b02c3514/tapestry-core/src/test/app1/PACMultipleAnnotationDemo.tml
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/app1/PACMultipleAnnotationDemo.tml b/tapestry-core/src/test/app1/PACMultipleAnnotationDemo.tml
new file mode 100644
index 0000000..0c8dc3b
--- /dev/null
+++ b/tapestry-core/src/test/app1/PACMultipleAnnotationDemo.tml
@@ -0,0 +1,16 @@
+<html t:type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+
+  <h1>Multiple @PageActivationContext Demo</h1>
+
+  PAC Values: <span id="pacValues">${pacValues}</span>
+
+  <ul>
+     <li><t:eventlink event="changePAC" context="[null, null, null]">Change PAC (null, null, null)</t:eventlink></li>
+     <li><t:eventlink event="changePAC" context="['zero', null, null]">Change PAC (zero, null, null)</t:eventlink></li>
+     <li><t:eventlink event="changePAC" context="['zero', 1, null]">Change PAC (zero, 1, null)</t:eventlink></li>
+     <li><t:eventlink event="changePAC" context="['zero', 1, 2.2]">Change PAC (zero, 1, 2.2)</t:eventlink></li>
+     <li><t:eventlink event="changePAC" context="['zero', 1, 2.2, 3]">Change PAC (zero, 1, 2.2, 3)</t:eventlink></li>
+     <li><t:eventlink event="changePAC" context="[null, null, 2.2]">Change PAC (null, null, 2.2)</t:eventlink></li>
+     <li><t:eventlink event="changePAC" context="['zero', null, 2.2]">Change PAC (zero, null, 2.2)</t:eventlink></li>
+  </ul>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b02c3514/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java
index 20703bb..3f2e5df 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CoreBehaviorsTests.java
@@ -1734,4 +1734,42 @@ public class CoreBehaviorsTests extends App1TestCase
     	assertTextPresent("description={'foo':'bar'},type=java.util.Map,genericType=interface java.util.Map");
     	assertTextPresent("description=baz,type=java.lang.String,genericType=class java.lang.String");
     }
+    
+    static class PacScenario {
+    	String link;
+    	String expextedPacValues;
+    	String expectedUri;
+    	
+		public PacScenario(String link, String expextedPacValues,
+				String expectedUri) {
+			super();
+			this.link = link;
+			this.expextedPacValues = expextedPacValues;
+			this.expectedUri = expectedUri;
+		}
+    }
+    
+    @Test
+    public void multiple_pac_fields()
+    {
+    	openLinks("PageActivationContext Multiple Demo");
+    	
+    	assertText("//span[@id='pacValues']", "zero=NULL, one=NULL, two=NULL");
+    	
+    	PacScenario[] scenarios = {
+			new PacScenario("link=Change PAC (null, null, null)", "zero=NULL, one=NULL, two=NULL", "/pacmultipleannotationdemo"),
+			new PacScenario("link=Change PAC (zero, null, null)", "zero=zero, one=NULL, two=NULL", "/pacmultipleannotationdemo/zero"),
+			new PacScenario("link=Change PAC (zero, 1, null)", "zero=zero, one=1, two=NULL", "/pacmultipleannotationdemo/zero/1"),
+			new PacScenario("link=Change PAC (zero, 1, 2.2)", "zero=zero, one=1, two=2.2", "/pacmultipleannotationdemo/zero/1/2.2"),
+			new PacScenario("link=Change PAC (zero, 1, 2.2, 3)", "zero=zero, one=1, two=2.2", "/pacmultipleannotationdemo/zero/1/2.2"),
+			new PacScenario("link=Change PAC (null, null, 2.2)", "zero=NULL, one=NULL, two=2.2", "/pacmultipleannotationdemo/$N/$N/2.2"),
+			new PacScenario("link=Change PAC (zero, null, 2.2)", "zero=zero, one=NULL, two=2.2", "/pacmultipleannotationdemo/zero/$N/2.2"),
+    	};
+    	
+    	for (PacScenario scenario : scenarios) {
+        	clickAndWait(scenario.link);
+        	assertText("//span[@id='pacValues']", scenario.expextedPacValues);
+        	assertTrue(selenium.getLocation().endsWith(scenario.expectedUri));
+    	}
+    }
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b02c3514/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
index 992bb34..85d0987 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
@@ -119,6 +119,9 @@ public class Index
                     new Item("PACAnnotationDemo", "PageActivationContext Demo",
                             "Shows that @PageActivationContext fields are set before calls to the activate event handler."),
 
+                    new Item("PACMultipleAnnotationDemo", "PageActivationContext Multiple Demo",
+                            "Demonstrates multiple @PageActivationContext fields."),
+                            
                     new Item("PublicFieldAccessDemo", "Public Field Access Demo", "Demonstrates TAP5-1222 fix"),
 
                     new Item("ActivationRequestParameterDemo", "ActivationRequestParameter Annotation Demo",

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/b02c3514/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PACMultipleAnnotationDemo.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PACMultipleAnnotationDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PACMultipleAnnotationDemo.java
new file mode 100644
index 0000000..c128936
--- /dev/null
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/PACMultipleAnnotationDemo.java
@@ -0,0 +1,51 @@
+// Copyright 2010 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.integration.app1.pages;
+
+import org.apache.tapestry5.annotations.InjectPage;
+import org.apache.tapestry5.annotations.PageActivationContext;
+
+public class PACMultipleAnnotationDemo {
+    @PageActivationContext(index=2)
+    private Double two;
+
+    @PageActivationContext(index=0)
+    private String zero;
+
+    @PageActivationContext(index=1)
+    private Integer one;
+
+    @InjectPage
+    private PACMultipleAnnotationDemo otherPage;
+
+    public void init(String zero, Integer one, Double two) {
+        this.zero = zero;
+        this.one = one;
+        this.two = two;
+    }
+
+    public String getPACValues() {
+        return String.format("zero=%s, one=%s, two=%s", toDisplayString(zero), toDisplayString(one), toDisplayString(two));
+    }
+
+    private String toDisplayString(Object o) {
+        return o == null ? "NULL" : o.toString();
+    }
+
+    public Object onChangePAC(String zero, Integer one, Double two) {
+        otherPage.init(zero, one, two);
+        return otherPage;
+    }
+}
\ No newline at end of file