Updated Branches: refs/heads/sandbox/component-queueing [created] a6d5adf67
wip on queueing, this time ignoring auto tags to see if that helps Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/a6d5adf6 Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/a6d5adf6 Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/a6d5adf6 Branch: refs/heads/sandbox/component-queueing Commit: a6d5adf677215aea0b7431c00b7aa48b959f4cda Parents: 30e9a19 Author: Igor Vaynberg <[email protected]> Authored: Fri Jan 27 13:29:01 2012 -0800 Committer: Igor Vaynberg <[email protected]> Committed: Fri Jan 27 13:29:01 2012 -0800 ---------------------------------------------------------------------- .../src/main/java/org/apache/wicket/Component.java | 27 +- .../java/org/apache/wicket/MarkupContainer.java | 283 +++++++++- .../parser/filter/StyleAndScriptIdentifier.java | 1 + .../wicket/queueing/ComponentQueueingTest.java | 456 +++++++++++++++ .../java/org/apache/wicket/queueing/HasPath.java | 119 ++++ .../org/apache/wicket/queueing/IsParentOf.java | 82 +++ .../test/java/org/apache/wicket/queueing/Path.java | 101 ++++ .../org/apache/wicket/queueing/WicketMatchers.java | 35 ++ 8 files changed, 1090 insertions(+), 14 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/a6d5adf6/wicket-core/src/main/java/org/apache/wicket/Component.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/Component.java b/wicket-core/src/main/java/org/apache/wicket/Component.java index d2f8ec4..a66fb10 100644 --- a/wicket-core/src/main/java/org/apache/wicket/Component.java +++ b/wicket-core/src/main/java/org/apache/wicket/Component.java @@ -302,7 +302,6 @@ public abstract class Component }; /** an unused flag */ - private static final int FLAG_UNUSED0 = 0x20000000; private static final int FLAG_UNUSED1 = 0x800000; private static final int FLAG_UNUSED2 = 0x1000000; private static final int FLAG_UNUSED3 = 0x10000000; @@ -390,6 +389,7 @@ public abstract class Component private static final int FLAG_AFTER_RENDERING = 0x8000000; private static final int FLAG_MARKUP_ATTACHED = 0x10000000; + private static final int FLAG_CHILDREN_DEQUEUED = 0x20000000; /** * Flag that restricts visibility of a component when set to true. This is usually used when a @@ -1072,6 +1072,7 @@ public abstract class Component { clearEnabledInHierarchyCache(); clearVisibleInHierarchyCache(); + internalDequeue(); onConfigure(); for (Behavior behavior : getBehaviors()) { @@ -1090,6 +1091,24 @@ public abstract class Component } } + private void internalDequeue() + { + if (!getFlag(FLAG_CHILDREN_DEQUEUED)) + { + dequeue(); + onHierarchyReady(); + setFlag(FLAG_CHILDREN_DEQUEUED, true); + } + } + + /** + * Called when the child hierarchy of this container is stable. Currently this means that any + * queued children have been dequeued + */ + protected void onHierarchyReady() + { + } + /** * * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT! @@ -4318,6 +4337,12 @@ public abstract class Component return isVisible() && isRenderAllowed() && isVisibilityAllowed(); } + /** + * Dequeues queued children + */ + void dequeue() + { + } /** * Calculates enabled state of the component taking its hierarchy into account. A component is http://git-wip-us.apache.org/repos/asf/wicket/blob/a6d5adf6/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java b/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java index 8b79831..692646c 100644 --- a/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java +++ b/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Stack; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.IMarkupFragment; @@ -35,7 +36,9 @@ import org.apache.wicket.markup.MarkupType; import org.apache.wicket.markup.WicketTag; import org.apache.wicket.markup.html.border.Border; import org.apache.wicket.markup.html.internal.InlineEnclosure; +import org.apache.wicket.markup.repeater.AbstractRepeater; import org.apache.wicket.markup.resolver.ComponentResolvers; +import org.apache.wicket.markup.resolver.IComponentResolver; import org.apache.wicket.model.IComponentInheritedModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.IWrapModel; @@ -55,12 +58,12 @@ import org.slf4j.LoggerFactory; /** * A MarkupContainer holds a map of child components. * <ul> - * <li><b>Children </b>- Children can be added by calling the {@link #add(Component...)} method, and they can be looked - * up using a colon separated path. For example, if a container called "a" held a nested container "b" which - * held a nested component "c", then a.get("b:c") would return the Component with id "c". The number - * of children in a MarkupContainer can be determined by calling size(), and the whole hierarchy of - * children held by a MarkupContainer can be traversed by calling visitChildren(), passing in an - * implementation of IVisitor. + * <li><b>Children </b>- Children can be added by calling the {@link #add(Component...)} method, and + * they can be looked up using a colon separated path. For example, if a container called "a" held a + * nested container "b" which held a nested component "c", then a.get("b:c") would return the + * Component with id "c". The number of children in a MarkupContainer can be determined by calling + * size(), and the whole hierarchy of children held by a MarkupContainer can be traversed by calling + * visitChildren(), passing in an implementation of IVisitor. * * <li><b>Markup Rendering </b>- A MarkupContainer also holds/references associated markup which is * used to render the container. As the markup stream for a container is rendered, component @@ -73,20 +76,21 @@ import org.slf4j.LoggerFactory; * graphic designers may be setting attributes on component tags that affect visual presentation. * <p> * The type of markup held in a given container subclass can be determined by calling - * {@link #getMarkupType()}. Markup is accessed via a MarkupStream object which allows a component to - * traverse ComponentTag and RawMarkup MarkupElements while rendering a response. Markup in the + * {@link #getMarkupType()}. Markup is accessed via a MarkupStream object which allows a component + * to traverse ComponentTag and RawMarkup MarkupElements while rendering a response. Markup in the * stream may be HTML or some other kind of markup, such as VXML, as determined by the specific * container subclass. * <p> * A markup stream may be directly associated with a container via setMarkupStream. However, a * container which does not have a markup stream (its getMarkupStream() returns null) may inherit a - * markup stream from a container above it in the component hierarchy. The {@link #findMarkupStream()} method - * will locate the first container at or above this container which has a markup stream. + * markup stream from a container above it in the component hierarchy. The + * {@link #findMarkupStream()} method will locate the first container at or above this container + * which has a markup stream. * <p> * All Page containers set a markup stream before rendering by calling the method - * {@link #getAssociatedMarkupStream(boolean)} to load the markup associated with the page. Since Page is at the top - * of the container hierarchy, it is guaranteed that {@link #findMarkupStream()} will always return a valid - * markup stream. + * {@link #getAssociatedMarkupStream(boolean)} to load the markup associated with the page. Since + * Page is at the top of the container hierarchy, it is guaranteed that {@link #findMarkupStream()} + * will always return a valid markup stream. * * @see MarkupStream * @author Jonathan Locke @@ -99,6 +103,10 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp /** Log for reporting. */ private static final Logger log = LoggerFactory.getLogger(MarkupContainer.class); + private static MetaDataKey<ArrayList<Component>> QUEUE = new MetaDataKey<ArrayList<Component>>() + { + }; + /** List of children or single child */ private Object children; @@ -1378,6 +1386,255 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp } } + + public MarkupContainer queue(final Component... childs) + { + ArrayList<Component> queue = getMetaData(QUEUE); + if (queue == null) + { + queue = new ArrayList<Component>(); + setMetaData(QUEUE, queue); + } + + if (getApplication().usesDevelopmentConfig()) + { + for (Component child : childs) + { + for (Component queued : queue) + { + if (queued.getId().equals(child.getId())) + { + throw new WicketRuntimeException( + "Component with id: '" + + queued.getId() + + "' is already queued in container: " + + this + + ". Two components with the same id cannot be queued under the same container. Component alread queued: " + + queued + ". Component attempted to be queued: " + child); + } + } + queue.add(child); + } + } + return this; + } + + @Override + void dequeue() + { + /* + * WARNING: THIS CODE IS EXTREMELY ROUGH AND DOES NOT FUNCTION IN THE SAME WAY THAT FINAL + * CODE WILL. IT IS HERE ONLY SO VARIOUS TESTS CAN BE WRITTEN IN HierarchyCompletionTest TO + * EXPLORE THIS IDEA + */ + + class ComponentAndTag + { + ComponentTag tag; + MarkupContainer component; + + public ComponentAndTag(ComponentTag tag, MarkupContainer component) + { + this.tag = tag; + this.component = component; + } + } + + MarkupStream markup = null; + + if (getAssociatedMarkup() != null) + { + markup = new MarkupStream(getMarkupSourcingStrategy().getMarkup(this, null)); + } + else if (getParent() instanceof AbstractRepeater) + { + markup = new MarkupStream(getParent().getMarkup()); + + // skip the repeater tag, we only want to traverse the body + markup.next(); + } + else + { + // we only complete the hierarchy of components with associated markup and direct + // children of repeaters. the rest of components should be inside the previous two + // types. + + return; + } + + // stack of components between the current tag in the markup and the markup's owner + Stack<ComponentAndTag> stack = new Stack<ComponentAndTag>(); + + // current component is the root of the stack. it has no component tag. + stack.push(new ComponentAndTag(null, this)); + + ComponentTag tag = null; + + while (markup.hasMore()) + { + if (tag != null) + { + // advance the markup stream if this is not the first time through + markup.next(); + } + if (!markup.skipUntil(ComponentTag.class)) + { + // TODO error if stack is not empty + break; + } + + // the current markup tag + tag = (ComponentTag)markup.get(); + + if (tag.isClose()) + { + if (stack.isEmpty()) + { + // we are now out of the markup owner's body markup, most likely on a + // </wicket:panel> or something similar, we are done + break; + // return; + } + if (tag.closes(stack.peek().tag)) + { + stack.pop(); + } + continue; + } + + if (tag.isAutoComponentTag()) + { + // we skip resolver-managed tags + continue; + } + + final MarkupContainer parent = stack.peek().component; + + // attempt to find a child component that corresponds to the markup tag + + // TODO this should be get or resolve so transparent things work + Component child = parent.get(tag.getId()); + + if (child == null) + { + // check if any of the parents are resolvers and attempt to locate the child that + // way + for (int j = stack.size() - 1; j >= 0; j--) + { + // we try to find a queued component from the deepest nested parent all the way + // to the owner of the markup + ComponentAndTag cat = stack.get(j); + Component cursor = cat.component; + if (cursor instanceof MarkupContainer && cursor instanceof IComponentResolver) + { + child = ((IComponentResolver)cursor).resolve((MarkupContainer)cursor, + markup, tag); + if (child.getParent() == null) + { + throw new IllegalStateException( + "Resolver created a new child, not sure this should be supported"); + } + if (child != null) + { + break; + } + } + } + } + + if (child == null) + { + // try to deque a child component if one has not been found + + + for (int j = stack.size() - 1; j >= 0; j--) + { + // we try to find a queued component from the deepest nested parent all the way + // to the owner of the markup + ComponentAndTag cat = stack.get(j); + ArrayList<Component> queue = cat.component.getMetaData(QUEUE); + if (queue == null) + { + continue; + } + + for (Component queued : queue) + { + if (queued.getId().equals(tag.getId())) + { + child = queued; + break; + } + } + if (child != null) + { + queue.remove(child); + break; + } + } + } + + if (child != null && child.getParent() == null) + { + + parent.add(child); + } + + if (child != null && child.isAuto()) + { + // TODO this is yet another hack, need to figure out how auto components fit into + // this and why they dont get correctly resolved second time around + child.setAuto(false); + } + + if (child == null) + { + // cannot resolve child component, error + + // TODO make the mesage less queue-dependent. should be the same message that we + // throw during render when we cant resolve a child + + String error = "Could not dequeue or resolve child: `" + tag.getId() + "`. "; + error += "Parent search stack: ["; + for (int j = stack.size() - 1; j >= 0; j--) + { + if (j < stack.size() - 1) + { + error += ", "; + } + ComponentAndTag cat = stack.get(j); + error += cat.component.getClass().getSimpleName() + "('" + + cat.component.getId() + "')"; + } + error += "]"; + throw new WicketRuntimeException(error); + } + + if (tag.isOpenClose()) + { + // if this is an open/close tag we are done + continue; + } + + if (child instanceof AbstractRepeater) + { + // TODO hack for repeaters, this will be delegated to repeaters themselves later + + // skip inner markup, it will be processed by repeater items + markup.skipToMatchingCloseTag(tag); + } + else if (child instanceof MarkupContainer) + { + stack.push(new ComponentAndTag(tag, (MarkupContainer)child)); + } + else + { + // the child is not a container so we can skip its inner markup + markup.skipToMatchingCloseTag(tag); + } + } + } + /** * @param component * Component being removed http://git-wip-us.apache.org/repos/asf/wicket/blob/a6d5adf6/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/StyleAndScriptIdentifier.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/StyleAndScriptIdentifier.java b/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/StyleAndScriptIdentifier.java index bcc8634..fd76d80 100644 --- a/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/StyleAndScriptIdentifier.java +++ b/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/StyleAndScriptIdentifier.java @@ -60,6 +60,7 @@ public final class StyleAndScriptIdentifier extends AbstractMarkupFilter { // Not needed, but must not be null tag.setId("_ScriptStyle"); + tag.setAutoComponentTag(true); tag.setModified(true); } http://git-wip-us.apache.org/repos/asf/wicket/blob/a6d5adf6/wicket-core/src/test/java/org/apache/wicket/queueing/ComponentQueueingTest.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/queueing/ComponentQueueingTest.java b/wicket-core/src/test/java/org/apache/wicket/queueing/ComponentQueueingTest.java new file mode 100755 index 0000000..f5029d9 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/queueing/ComponentQueueingTest.java @@ -0,0 +1,456 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.wicket.queueing; + +import static org.apache.wicket.queueing.WicketMatchers.hasPath; +import static org.hamcrest.Matchers.is; + +import java.util.ArrayList; + +import org.apache.wicket.Component; +import org.apache.wicket.MarkupContainer; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.WicketTestCase; +import org.apache.wicket.markup.IMarkupResourceStreamProvider; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.WebPage; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.model.Model; +import org.apache.wicket.util.resource.IResourceStream; +import org.apache.wicket.util.resource.StringResourceStream; +import org.junit.Assert; +import org.junit.Test; + +public class ComponentQueueingTest extends WicketTestCase +{ + /** {@code [a,b,c] -> [a[b[c]]] } */ + @Test + public void dequeue1() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='b'><p wicket:id='c'></p></p></p>"); + MarkupContainer a = new A(), b = new B(), c = new C(); + + p.queue(b, c, a); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(a, b, c))); + } + + /** {@code [a[b,c]] -> [a[b[c]]] } */ + @Test + public void dequeue2() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='b'><p wicket:id='c'></p></p></p>"); + MarkupContainer a = new A(), b = new B(), c = new C(); + + p.queue(a); + a.queue(b, c); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(a, b, c))); + } + + /** {@code [a[b[c]] -> [a[b[c]]] } */ + @Test + public void dequeue3() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='b'><p wicket:id='c'></p></p></p>"); + MarkupContainer a = new A(), b = new B(), c = new C(); + + p.queue(a); + a.queue(b); + b.queue(c); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(a, b, c))); + } + + /** {@code [a[b],c] -> [a[b[c]]] } */ + @Test + public void dequeue4() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='b'><p wicket:id='c'></p></p></p>"); + MarkupContainer a = new A(), b = new B(), c = new C(); + + p.queue(a, c); + a.queue(b); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(a, b, c))); + } + + /** {@code [a(b)],c] -> [a[b[c]]] } */ + @Test + public void dequeue5() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='b'><p wicket:id='c'></p></p></p>"); + MarkupContainer a = new A(), b = new B(), c = new C(); + p.queue(a, c); + a.add(b); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(a, b, c))); + } + + /** {@code [a,b,c] -> [a[b,c]] } */ + @Test + public void dequeue6() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='b'></p><p wicket:id='c'></p></p>"); + MarkupContainer a = new A(), b = new B(), c = new C(); + + p.queue(a, b, c); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(a, b))); + assertThat(p, hasPath(new Path(a, c))); + } + + /** {@code [a,c[b]] ->| [a[b[c]]] } */ + @Test + public void dequeueError1() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='b'><p wicket:id='c'></p></p></p>"); + MarkupContainer a = new A(), b = new B(), c = new C(); + + p.queue(b, c); + c.queue(a); + + try + { + tester.startPage(p); + Assert.fail(); + } + catch (WicketRuntimeException e) + { + // expected + } + } + + /** {@code [a,q[r,s]] - > [a[q[r[s]]]] } */ + @Test + public void dequeueWithPanel1() + { + MarkupContainer a = new A(), r = new R(), s = new S(); + + TestPanel q = new TestPanel("q"); + q.setPanelMarkup("<wicket:panel><p wicket:id='r'><p wicket:id='s'></p></p></wicket:panel>"); + q.queue(r, s); + + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='q'></p></p>"); + + p.queue(a, q); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(a, q, r, s))); + } + + /** panel has leading markup */ + @Test + public void dequeueWithPanel2() + { + MarkupContainer r = new R(); + + TestPanel q = new TestPanel("q"); + q.setPanelMarkup("<html><body><wicket:panel><p wicket:id='r'></p></wicket:panel></body></html>"); + q.queue(r); + + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='q'></p>"); + p.queue(q); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(q, r))); + } + + /** panel with a static header section */ + @Test + public void dequeueWithPanel3() + { + MarkupContainer r = new R(); + + TestPanel q = new TestPanel("q"); + q.setPanelMarkup("<html><head><wicket:head><meta/></wicket:head></head>" + + "<body><wicket:panel><p wicket:id='r'></p></wicket:panel></body></html>"); + q.queue(r); + + TestPage p = new TestPage(); + p.setPageMarkup("<html><head></head><body><p wicket:id='q'></p></body></html>"); + p.queue(q); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(q, r))); + } + + /** repeater */ + @Test + public void dequeueWithRepeater1() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='lv'><p wicket:id='b'><p wicket:id='c'></p></p></p></p>"); + + MarkupContainer a = new A(); + LV l = new LV(3) + { + @Override + protected void populateItem(ListItem<Integer> item) + { + item.queue(new B(), new C()); + } + }; + + p.queue(a, l); + + tester.startPage(p); + + assertThat(l.size(), is(3)); + for (Component item : l) + { + assertThat(p, hasPath(new Path(a, l, item, new B(), new C()))); + } + } + + /** repeater with a panel inside */ + @Test + public void dequeueWithRepeater2() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><p wicket:id='lv'><p wicket:id='b'><p wicket:id='q'></p></p></p></p>"); + + MarkupContainer a = new A(); + LV l = new LV(3) + { + @Override + protected void populateItem(ListItem<Integer> item) + { + TestPanel q = new TestPanel("q"); + q.setPanelMarkup("<wicket:panel><p wicket:id='r'><p wicket:id='s'></p></p></wicket:panel>"); + q.queue(new R(), new S()); + + item.queue(q, new B()); + } + }; + + p.queue(a, l); + + tester.startPage(p); + + assertThat(l.size(), is(3)); + for (Component item : l) + { + assertThat(p, hasPath(new Path(a, l, item, new B()).add("q").add(new R(), new S()))); + } + } + + /** dequeue, then rerender the page instance after a callback is executed */ + @Test + public void dequeueWithCallback() + { + TestPage p = new TestPage(); + p.setPageMarkup("<p wicket:id='a'><a wicket:id='l'><p wicket:id='b'></p></a></p>"); + MarkupContainer a = new A(), b = new B(); + L l = new L(); + p.queue(a, b, l); + + tester.startPage(p); + + assertThat(p, hasPath(new Path(a, l, b))); + assertThat(l.isClicked(), is(false)); + + tester.clickLink(l); + + assertThat(l.isClicked(), is(true)); + } + + + /** queuing two components with the same id */ + @Test + public void queueIdCollission() + { + try + { + new A().queue(new B(), new B()); + Assert.fail("Should not be able to queue two components with the same id under the same parent"); + } + catch (WicketRuntimeException e) + { + // expected + } + } + + + private static class A extends WebMarkupContainer + { + public A() + { + super("a"); + } + } + + private static class B extends WebMarkupContainer + { + public B() + { + super("b"); + } + } + + private static class C extends WebMarkupContainer + { + public C() + { + super("c"); + } + } + + private static class R extends WebMarkupContainer + { + public R() + { + super("r"); + } + } + + private static class S extends WebMarkupContainer + { + public S() + { + super("s"); + } + } + + private static abstract class LV extends ListView<Integer> + { + public LV(int size) + { + super("lv"); + ArrayList<Integer> values = new ArrayList<Integer>(); + for (int i = 0; i < size; i++) + values.add(i); + setModel(new Model<ArrayList<Integer>>(values)); + } + } + + private static class L extends Link<Void> + { + private boolean clicked = false; + + public L() + { + super("l"); + } + + @Override + public void onClick() + { + clicked = true; + } + + public boolean isClicked() + { + return clicked; + } + } + + + private static class TestPage extends WebPage implements IMarkupResourceStreamProvider + { + private String markup; + + public TestPage() + { + } + + public TestPage(String markup) + { + this.markup = markup; + } + + protected String getPageMarkup() + { + return markup; + } + + public void setPageMarkup(String markup) + { + this.markup = markup; + } + + @Override + public IResourceStream getMarkupResourceStream(MarkupContainer container, + Class<?> containerClass) + { + return new StringResourceStream(getPageMarkup()); + } + + } + + private static class TestPanel extends Panel implements IMarkupResourceStreamProvider + { + + private String markup; + + public TestPanel(String id) + { + super(id); + } + + public TestPanel(String id, String markup) + { + super(id); + this.markup = markup; + } + + protected void setPanelMarkup(String markup) + { + this.markup = markup; + } + + protected String getPanelMarkup() + { + return markup; + } + + @Override + public IResourceStream getMarkupResourceStream(MarkupContainer container, + Class<?> containerClass) + { + return new StringResourceStream(getPanelMarkup()); + } + + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/a6d5adf6/wicket-core/src/test/java/org/apache/wicket/queueing/HasPath.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/queueing/HasPath.java b/wicket-core/src/test/java/org/apache/wicket/queueing/HasPath.java new file mode 100755 index 0000000..f4751cb --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/queueing/HasPath.java @@ -0,0 +1,119 @@ +package org.apache.wicket.queueing; + +import org.apache.wicket.Component; +import org.apache.wicket.MarkupContainer; +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +class HasPath extends TypeSafeMatcher<Component> +{ + private final Path path; + + public HasPath(Path path) + { + this.path = path; + } + + public void describeTo(Description description) + { + description.appendText("path ").appendText(toString(path, 0, path.size())); + } + + @Override + protected boolean matchesSafely(Component item) + { + Component cursor = item; + for (int i = 0; i < path.size(); i++) + { + if (!(cursor instanceof MarkupContainer)) + { + return false; + } + + cursor = ((MarkupContainer)cursor).get(path.get(i).getId()); + if (cursor == null) + { + return false; + } + if (!path.get(i).getType().isAssignableFrom(cursor.getClass())) + { + return false; + } + } + return true; + } + + @Override + public void describeMismatchSafely(Component item, Description desc) + { + Component cursor = item; + + int matched = 0; + + String error = null; + + for (int i = 0; i < path.size(); i++) + { + matched = i; + if (!(cursor instanceof MarkupContainer)) + { + error = "next component has to be at least a MarkupContainer to contain children, but was: " + + toString(cursor); + break; + } + + cursor = ((MarkupContainer)cursor).get(path.get(i).getId()); + if (cursor == null) + { + error = "next child with id: '" + path.get(i).getId() + "' not found"; + break; + } + if (!path.get(i).getType().isAssignableFrom(cursor.getClass())) + { + error = "expected next child of type: " + path.get(i).getType().getSimpleName() + + ", but found: " + toString(cursor); + break; + } + } + + desc.appendText("\n root: ").appendText(toString(item)); + desc.appendText("\n matched segments: ").appendText(toString(path, 0, matched)); + desc.appendText("\n error: ").appendText(error); + } + + private static String toString(Component c) + { + return toString(c.getClass(), c.getId()); + } + + private static String toString(Class<?> type, String id) + { + return type.getSimpleName() + "('" + id + "')"; + } + + private static String toString(Path path, int start, int end) + { + String str = "["; + for (int i = start; i < end; i++) + { + if (i > 0) + { + str += ", "; + } + str += toString(path.get(i).getType(), path.get(i).getId()); + } + str += "]"; + return str; + } + + + @Factory + public static <T> Matcher<Component> hasPath(Path path) + { + return new HasPath(path); + } + + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/a6d5adf6/wicket-core/src/test/java/org/apache/wicket/queueing/IsParentOf.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/queueing/IsParentOf.java b/wicket-core/src/test/java/org/apache/wicket/queueing/IsParentOf.java new file mode 100755 index 0000000..e38d385 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/queueing/IsParentOf.java @@ -0,0 +1,82 @@ +package org.apache.wicket.queueing; + +import org.apache.wicket.Component; +import org.apache.wicket.MarkupContainer; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +class IsParentOf extends TypeSafeMatcher<Component> +{ + private final Component child; + + public IsParentOf(Component child) + { + this.child = child; + } + + public void describeTo(Description description) + { + description.appendText(toString(child.getParent())); + } + + @Override + protected boolean matchesSafely(Component item) + { + if (!(item instanceof MarkupContainer)) + { + return false; + } + + if (!(item instanceof MarkupContainer)) + return false; + MarkupContainer container = (MarkupContainer)item; + if (container.get(child.getId()) != child) + return false; + if (child.getParent() != container) + return false; + return true; + } + + @Override + public void describeMismatchSafely(Component item, Description description) + { + if (child.getParent() != item) + { + description.appendText("found " + toString(item)); + return; + } + + if (!(item instanceof MarkupContainer)) + { + description.appendText("found ") + .appendText(toString(item)) + .appendText(" which is not a container"); + return; + } + + if (((WebMarkupContainer)item).get(child.getId()) == null) + { + description.appendText(toString(item)) + .appendText(" does not contain ") + .appendText(toString(child)); + return; + } + super.describeMismatchSafely(item, description); + } + + private static String toString(Component c) + { + return c.getClass().getSimpleName() + "('" + c.getId() + "')"; + } + + @Factory + public static <T> Matcher<Component> isParentOf(Component child) + { + return new IsParentOf(child); + } + + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/a6d5adf6/wicket-core/src/test/java/org/apache/wicket/queueing/Path.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/queueing/Path.java b/wicket-core/src/test/java/org/apache/wicket/queueing/Path.java new file mode 100755 index 0000000..5582260 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/queueing/Path.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.wicket.queueing; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.wicket.Component; + +class Path implements Iterable<Path.Segment> +{ + + private List<Segment> segments; + + public Path() + { + segments = new ArrayList<Segment>(); + } + + public Path(Component... components) + { + this(); + add(components); + } + + public Path add(Class<?> type, String id) + { + segments.add(new Segment(type, id)); + return this; + } + + public Path add(String id) + { + add(Component.class, id); + return this; + } + + public Path add(Component... components) + { + for (Component c : components) + { + add(c.getClass(), c.getId()); + } + return this; + } + + + @Override + public Iterator<Path.Segment> iterator() + { + return segments.iterator(); + } + + public int size() + { + return segments.size(); + } + + public Segment get(int index) + { + return segments.get(index); + } + + public static class Segment + { + Class<?> type; + String id; + + public Segment(Class<?> type, String id) + { + this.type = type; + this.id = id; + } + + public Class<?> getType() + { + return type; + } + + public String getId() + { + return id; + } + + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/a6d5adf6/wicket-core/src/test/java/org/apache/wicket/queueing/WicketMatchers.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/queueing/WicketMatchers.java b/wicket-core/src/test/java/org/apache/wicket/queueing/WicketMatchers.java new file mode 100755 index 0000000..e8d58c4 --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/queueing/WicketMatchers.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.wicket.queueing; + +import org.apache.wicket.Component; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; + +class WicketMatchers extends Matchers +{ + public static <T> Matcher<Component> isParentOf(Component child) + { + return new IsParentOf(child); + } + + + public static <T> Matcher<Component> hasPath(Path path) + { + return new HasPath(path); + } +} \ No newline at end of file
