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 <[email protected]> Authored: Thu Jun 26 19:42:29 2014 +0100 Committer: uklance <[email protected]> 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
