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

Reply via email to