You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by iv...@apache.org on 2012/01/07 06:33:23 UTC
git commit: queuing wip
Updated Branches:
refs/heads/sandbox/queueing [created] eab817441
queuing wip
Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/eab81744
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/eab81744
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/eab81744
Branch: refs/heads/sandbox/queueing
Commit: eab8174416fe11dd2e23ecea2cf94a1d08cce378
Parents: 061a478
Author: Igor Vaynberg <iv...@apache.org>
Authored: Fri Jan 6 21:32:23 2012 -0800
Committer: Igor Vaynberg <iv...@apache.org>
Committed: Fri Jan 6 21:32:23 2012 -0800
----------------------------------------------------------------------
.../src/main/java/org/apache/wicket/Component.java | 21 +-
.../java/org/apache/wicket/MarkupContainer.java | 45 ++
.../markup/queueing/ComponentQueueingTest.java | 456 +++++++++++++++
.../org/apache/wicket/markup/queueing/HasPath.java | 119 ++++
.../apache/wicket/markup/queueing/IsParentOf.java | 82 +++
.../org/apache/wicket/markup/queueing/Path.java | 103 ++++
.../wicket/markup/queueing/WicketMatchers.java | 35 ++
7 files changed, 853 insertions(+), 8 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/wicket/blob/eab81744/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 8725706..aefc425 100644
--- a/wicket-core/src/main/java/org/apache/wicket/Component.java
+++ b/wicket-core/src/main/java/org/apache/wicket/Component.java
@@ -104,8 +104,7 @@ import org.slf4j.LoggerFactory;
* concatenation with colon separators of each id along the way. For example, the path "a:b:c" would
* refer to the component named "c" inside the MarkupContainer named "b" inside the container named
* "a". The path to a component can be retrieved by calling {@link #getPath()}. To get a Component
- * path relative to the page that contains it, you can call {@link #getPageRelativePath()}.
- * </li>
+ * path relative to the page that contains it, you can call {@link #getPageRelativePath()}.</li>
* <li><b>LifeCycle </b>- Components participate in the following lifecycle phases:
* <ul>
* <li><b>Construction </b>- A Component is constructed with the Java language new operator.
@@ -113,8 +112,7 @@ import org.slf4j.LoggerFactory;
* {@link IComponentInstantiationListener}s are notified of component instantiation.
* <p>
* {@link #onInitialize()} is called on the component as soon as the component is part of a page's
- * component tree. At this state the component is able to access its markup.
- * </li>
+ * component tree. At this state the component is able to access its markup.</li>
* <li><b>Request Handling </b>- An incoming request is processed by a protocol request handler such
* as {@link WicketFilter}. An associated Application object creates {@link Session},
* {@link Request} and {@link Response} objects for use by a given Component in updating its model
@@ -973,6 +971,7 @@ public abstract class Component
getApplication().getComponentPreOnBeforeRenderListeners().onBeforeRender(this);
onBeforeRender();
+
getApplication().getComponentPostOnBeforeRenderListeners().onBeforeRender(this);
if (!getRequestFlag(RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED))
@@ -1074,6 +1073,7 @@ public abstract class Component
{
clearEnabledInHierarchyCache();
clearVisibleInHierarchyCache();
+ dequeue();
onConfigure();
for (Behavior behavior : getBehaviors())
{
@@ -1090,6 +1090,10 @@ public abstract class Component
}
}
+ void dequeue()
+ {
+ }
+
/**
* Redirects to any intercept page previously specified by a call to redirectToInterceptPage.
* The redirect is done by throwing an exception. If there is no intercept page no exception
@@ -2678,10 +2682,11 @@ public abstract class Component
// to be backward compatible. WICKET-3761
getMarkupSourcingStrategy().renderHead(this, container);
CharSequence headerContribution = markupHeaderResponse.getBuffer();
- if (Strings.isEmpty(headerContribution) == false) {
+ if (Strings.isEmpty(headerContribution) == false)
+ {
response.render(StringHeaderItem.forString(headerContribution));
}
- }
+ }
finally
{
RequestCycle.get().setResponse(oldResponse);
@@ -3309,7 +3314,7 @@ public abstract class Component
{
return getRequestCycle().urlFor(pageClass, parameters);
}
-
+
/**
* Gets a URL for the listener interface on a behavior (e.g. IBehaviorListener on
* AjaxPagingNavigationBehavior).
@@ -3354,7 +3359,7 @@ public abstract class Component
{
return getRequestCycle().urlFor(requestHandler);
}
-
+
/**
* Gets a URL for the listener interface (e.g. ILinkListener).
*
http://git-wip-us.apache.org/repos/asf/wicket/blob/eab81744/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 621ea05..d35051c 100644
--- a/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java
+++ b/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java
@@ -99,6 +99,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;
@@ -303,6 +307,47 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp
return true;
}
+ 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;
+ }
+
+ /**
+ * Implements the process to dequeue all components registered
+ */
+ @Override
+ void dequeue()
+ {
+ // FIXME
+ }
+
/**
* @param component
* The component to check
http://git-wip-us.apache.org/repos/asf/wicket/blob/eab81744/wicket-core/src/test/java/org/apache/wicket/markup/queueing/ComponentQueueingTest.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/queueing/ComponentQueueingTest.java b/wicket-core/src/test/java/org/apache/wicket/markup/queueing/ComponentQueueingTest.java
new file mode 100755
index 0000000..939ba32
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/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.markup.queueing;
+
+import static org.apache.wicket.markup.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/eab81744/wicket-core/src/test/java/org/apache/wicket/markup/queueing/HasPath.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/queueing/HasPath.java b/wicket-core/src/test/java/org/apache/wicket/markup/queueing/HasPath.java
new file mode 100755
index 0000000..dac0d12
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/queueing/HasPath.java
@@ -0,0 +1,119 @@
+package org.apache.wicket.markup.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/eab81744/wicket-core/src/test/java/org/apache/wicket/markup/queueing/IsParentOf.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/queueing/IsParentOf.java b/wicket-core/src/test/java/org/apache/wicket/markup/queueing/IsParentOf.java
new file mode 100755
index 0000000..33da758
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/queueing/IsParentOf.java
@@ -0,0 +1,82 @@
+package org.apache.wicket.markup.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/eab81744/wicket-core/src/test/java/org/apache/wicket/markup/queueing/Path.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/queueing/Path.java b/wicket-core/src/test/java/org/apache/wicket/markup/queueing/Path.java
new file mode 100755
index 0000000..96f6ab8
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/queueing/Path.java
@@ -0,0 +1,103 @@
+/*
+ * 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.markup.queueing;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.queueing.Path.Segment;
+
+class Path implements Iterable<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<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/eab81744/wicket-core/src/test/java/org/apache/wicket/markup/queueing/WicketMatchers.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/queueing/WicketMatchers.java b/wicket-core/src/test/java/org/apache/wicket/markup/queueing/WicketMatchers.java
new file mode 100755
index 0000000..d379a1a
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/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.markup.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