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