Updated Branches:
  refs/heads/sandbox/hierarchy-completion [created] 214c95ca4


wip impl of hierarchy-completion


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/214c95ca
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/214c95ca
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/214c95ca

Branch: refs/heads/sandbox/hierarchy-completion
Commit: 214c95ca4a33e08b003fc46fc62ea8da181e739e
Parents: 407503d
Author: Igor Vaynberg <[email protected]>
Authored: Sun Dec 25 09:21:31 2011 -0800
Committer: Igor Vaynberg <[email protected]>
Committed: Sun Dec 25 09:21:31 2011 -0800

----------------------------------------------------------------------
 .../src/main/java/org/apache/wicket/Component.java |   18 +-
 .../java/org/apache/wicket/MarkupContainer.java    |  375 ++++++++++-
 .../apache/wicket/hierarchy/complete/HasPath.java  |  119 ++++
 .../complete/HierarchyCompletionTest.java          |  515 +++++++++++++++
 .../wicket/hierarchy/complete/IsParentOf.java      |   82 +++
 .../org/apache/wicket/hierarchy/complete/Path.java |  103 +++
 .../wicket/hierarchy/complete/TesterRule.java      |   96 +++
 .../wicket/hierarchy/complete/WicketMatchers.java  |   35 +
 8 files changed, 1299 insertions(+), 44 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/214c95ca/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 4c25361..c335ddc 100644
--- a/wicket-core/src/main/java/org/apache/wicket/Component.java
+++ b/wicket-core/src/main/java/org/apache/wicket/Component.java
@@ -892,20 +892,16 @@ public abstract class Component
                return getFlag(FLAG_INITIALIZED);
        }
 
-       /**
-        * THIS METHOD IS NOT PART OF THE PUBLIC API, DO NOT CALL IT
-        * 
-        * Used to call {@link #onInitialize()}
-        */
-       public void internalInitialize()
+       void completeHierarchy()
        {
-               fireInitialize();
        }
 
        /**
+        * THIS METHOD IS NOT PART OF THE PUBLIC API, DO NOT CALL IT
+        * 
         * Used to call {@link #onInitialize()}
         */
-       final void fireInitialize()
+       public void internalInitialize()
        {
                if (!getFlag(FLAG_INITIALIZED))
                {
@@ -961,7 +957,7 @@ public abstract class Component
        /**
         * 
         */
-       private final void internalBeforeRender()
+       final void internalBeforeRender()
        {
                configure();
 
@@ -973,6 +969,9 @@ public abstract class Component
                        
getApplication().getComponentPreOnBeforeRenderListeners().onBeforeRender(this);
 
                        onBeforeRender();
+
+                       completeHierarchy();
+
                        
getApplication().getComponentPostOnBeforeRenderListeners().onBeforeRender(this);
 
                        if 
(!getRequestFlag(RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED))
@@ -1070,6 +1069,7 @@ public abstract class Component
         */
        public final void configure()
        {
+               internalInitialize();
                if (!getRequestFlag(RFLAG_CONFIGURED))
                {
                        clearEnabledInHierarchyCache();

http://git-wip-us.apache.org/repos/asf/wicket/blob/214c95ca/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..7b4a1ca 100644
--- a/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java
+++ b/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java
@@ -16,12 +16,14 @@
  */
 package org.apache.wicket;
 
+import java.io.Serializable;
 import java.util.AbstractList;
 import java.util.ArrayList;
 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,6 +37,7 @@ 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.model.IComponentInheritedModel;
 import org.apache.wicket.model.IModel;
@@ -96,6 +99,10 @@ public abstract class MarkupContainer extends Component 
implements Iterable<Comp
 {
        private static final long serialVersionUID = 1L;
 
+       private static MetaDataKey<ArrayList<Component>> QUEUE = new 
MetaDataKey<ArrayList<Component>>()
+       {
+       };
+
        /** Log for reporting. */
        private static final Logger log = 
LoggerFactory.getLogger(MarkupContainer.class);
 
@@ -118,6 +125,305 @@ public abstract class MarkupContainer extends Component 
implements Iterable<Comp
                super(id, model);
        }
 
+       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;
+       }
+
+       // TODO hierarchy-completion
+       public static class ResolvedMeta implements Serializable
+       {
+               private String tagComponentId;
+               private int index;
+
+               public ResolvedMeta(MarkupStream stream)
+               {
+                       tagComponentId = ((ComponentTag)stream.get()).getId();
+                       index = stream.getCurrentIndex();
+               }
+
+               public boolean isFor(MarkupStream stream)
+               {
+                       if (stream.getCurrentIndex() != index)
+                       {
+                               return false;
+                       }
+                       if 
(!tagComponentId.equals(((ComponentTag)stream.get()).getId()))
+                       {
+                               return false;
+                       }
+                       return true;
+               }
+       }
+
+       public static final MetaDataKey<ResolvedMeta> RESOLVED_KEY = new 
MetaDataKey<ResolvedMeta>()
+       {
+
+       };
+
+       /**
+        * Completes component's hierarchy
+        * 
+        * @see org.apache.wicket.Component#completeHierarchy()
+        */
+       @Override
+       void completeHierarchy()
+       {
+               /*
+                * 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 (hasAssociatedMarkup())
+               {
+                       markup = new 
MarkupStream(getMarkupSourcingStrategy().getMarkup(this, null));
+                       // FIXME a hack to skip the first tag that does not 
resolve to any component such as
+                       // wicket:panel or wicket:border
+                       MarkupElement e = markup.get();
+                       if (e instanceof WicketTag)
+                       {
+                               if (((WicketTag)e).isMajorWicketComponentTag())
+                               {
+                                       markup.next();
+                               }
+                       }
+               }
+               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
+                               return;
+                       }
+
+                       // 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
+                                       return;
+                               }
+                               stack.pop();
+                               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)
+                       {
+                               // 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;
+                                       }
+                               }
+                       }
+
+                       for (Component potential : parent)
+                       {
+                               ResolvedMeta meta = 
potential.getMetaData(RESOLVED_KEY);
+                               if (meta != null)
+                               {
+                                       int a = 2;
+                                       int b = a + 2;
+                               }
+                               if (meta != null && meta.isFor(markup))
+                               {
+                                       child = potential;
+                                       break;
+                               }
+                       }
+
+                       if (child == null)
+                       {
+                               // if we didnt find a queued child we could use 
try the resolvers
+
+                               child = ComponentResolvers.resolve(parent, 
markup, tag, null);
+                               if (child != null)
+                               {
+                                       // tag.setId(child.getId());
+                                       // tag.setModified(true);
+                                       child.setMetaData(RESOLVED_KEY, new 
ResolvedMeta(markup));
+                               }
+                       }
+
+                       if (child != null && child.getParent() == null)
+                       {
+                               lateAdd(parent, child);
+                               // TODO do we need to continue unqueuing or can 
we skip this component
+                               // and all its children if it has been deemed 
invisible? - dont think we can because
+                               // that will leave components in the queue and 
ondetach() will bomb
+                       }
+
+                       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);
+                       }
+               }
+       }
+
+       private void lateAdd(MarkupContainer parent, Component queued)
+       {
+               parent.add(queued);
+
+               // at this point queued.onInitialize() wouldve been called by 
add()
+
+               if (parent.isVisibleInHierarchy())
+               {
+                       // call configure() and onbeforerender() which are done 
from inside internalBeforeRender
+                       queued.internalBeforeRender();
+               }
+       }
+
        /**
         * Adds a child component to this container.
         * 
@@ -973,19 +1279,6 @@ public abstract class MarkupContainer extends Component 
implements Iterable<Comp
                                ComponentStrings.toString(child, new 
MarkupException("added")));
                }
 
-               final Page page = findPage();
-               if (page != null)
-               {
-                       // tell the page a component has been added first, to 
allow it to initialize
-                       page.componentAdded(child);
-
-                       // initialize the component
-                       if (page.isInitialized())
-                       {
-                               child.internalInitialize();
-                       }
-               }
-
                // if the PREPARED_FOR_RENDER flag is set, we have already 
called
                // beforeRender on this component's children. So we need to 
initialize the newly added one
                if (isPreparedForRender())
@@ -995,28 +1288,6 @@ public abstract class MarkupContainer extends Component 
implements Iterable<Comp
        }
 
        /**
-        * THIS METHOD IS NOT PART OF THE PUBLIC API, DO NOT CALL IT
-        * 
-        * Overrides {@link Component#internalInitialize()} to call {@link 
Component#fireInitialize()}
-        * for itself and for all its children.
-        * 
-        * @see org.apache.wicket.Component#fireInitialize()
-        */
-       @Override
-       public final void internalInitialize()
-       {
-               super.fireInitialize();
-               visitChildren(new IVisitor<Component, Void>()
-               {
-                       @Override
-                       public void component(final Component component, final 
IVisit<Void> visit)
-                       {
-                               component.fireInitialize();
-                       }
-               });
-       }
-
-       /**
         * @param child
         *            Child to add
         */
@@ -1426,6 +1697,18 @@ public abstract class MarkupContainer extends Component 
implements Iterable<Comp
                        Component component = get(id);
                        if (component == null)
                        {
+                               for (Component c : this)
+                               {
+                                       ResolvedMeta meta = 
c.getMetaData(RESOLVED_KEY);
+                                       if (meta != null && 
meta.isFor(markupStream))
+                                       {
+                                               component = c;
+                                               break;
+                                       }
+                               }
+                       }
+                       if (component == null)
+                       {
                                component = ComponentResolvers.resolve(this, 
markupStream, tag, null);
                                if ((component != null) && 
(component.getParent() == null))
                                {
@@ -1647,6 +1930,28 @@ public abstract class MarkupContainer extends Component 
implements Iterable<Comp
        }
 
        @Override
+       protected void onDetach()
+       {
+               super.onDetach();
+               detachQueue();
+       }
+
+       private void detachQueue()
+       {
+               List<Component> queue = getMetaData(QUEUE);
+               if (queue != null)
+               {
+
+                       if (!queue.isEmpty())
+                       {
+                               // FIXME QUEUEING error message
+                               throw new WicketRuntimeException("SOME 
COMPONENTS WERE NOT DEQUEUED");
+                       }
+                       setMetaData(QUEUE, null);
+               }
+       }
+
+       @Override
        void detachChildren()
        {
                super.detachChildren();

http://git-wip-us.apache.org/repos/asf/wicket/blob/214c95ca/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/HasPath.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/HasPath.java 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/HasPath.java
new file mode 100755
index 0000000..a8ab2d7
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/HasPath.java
@@ -0,0 +1,119 @@
+package org.apache.wicket.hierarchy.complete;
+
+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/214c95ca/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/HierarchyCompletionTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/HierarchyCompletionTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/HierarchyCompletionTest.java
new file mode 100755
index 0000000..b966af0
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/HierarchyCompletionTest.java
@@ -0,0 +1,515 @@
+/*
+ * 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.hierarchy.complete;
+
+import static org.apache.wicket.hierarchy.complete.WicketMatchers.hasPath;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.WicketRuntimeException;
+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.Rule;
+import org.junit.Test;
+
+public class HierarchyCompletionTest
+{
+       @Rule
+       public TesterRule tester = new TesterRule();
+
+
+       /** {@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);
+                       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.getTester().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());
+                       fail("Should not be able to queue two components with 
the same id under the same parent");
+               }
+               catch (WicketRuntimeException e)
+               {
+                       // expected
+               }
+       }
+
+       @Test
+       public void resolveHeader1()
+       {
+               TestPage p = new TestPage();
+               
p.setPageMarkup("<html><head><wicket:head></wicket:head></head><body></body></html>");
+
+               tester.startPage(p);
+       }
+
+       /** resolve header, then rerender the page instance after a callback is 
executed */
+       @Test
+       public void resolveHeaderWithCallback()
+       {
+               TestPage p = new TestPage();
+               p.setPageMarkup("<html><head><wicket:head><wicket:link><a 
href='Foo.html'>foo</a></wicket:link></wicket:head></head>"
+                       + "<body><p wicket:id='a'><a wicket:id='l'><p 
wicket:id='b'></p></a></p></body></html>");
+               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.getTester().clickLink(l);
+
+               assertThat(l.isClicked(), is(true));
+       }
+
+       @Test
+       public void resolveHeaderWithRepeatedRenderOfSameInstance()
+       {
+               TestPage p = new TestPage();
+               p.setPageMarkup("<html><head><wicket:head><wicket:link><a 
href='Foo.html'>foo</a></wicket:link></wicket:head></head><body></body></html>");
+
+               tester.startPage(p);
+               tester.startPage(p);
+       }
+
+       @Test
+       public void resolveHeaderWithRepeatedRenderOfNewInstances()
+       {
+               class MyPage extends TestPage
+               {
+                       public MyPage()
+                       {
+                               
setPageMarkup("<html><head><wicket:head><wicket:link><a 
href='Foo.html'>foo</a></wicket:link></wicket:head></head><body></body></html>");
+                       }
+               }
+               tester.startPage(new MyPage());
+               tester.startPage(new MyPage());
+       }
+
+
+       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/214c95ca/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/IsParentOf.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/IsParentOf.java
 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/IsParentOf.java
new file mode 100755
index 0000000..ed978e6
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/IsParentOf.java
@@ -0,0 +1,82 @@
+package org.apache.wicket.hierarchy.complete;
+
+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/214c95ca/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/Path.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/Path.java 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/Path.java
new file mode 100755
index 0000000..8b25bd2
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/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.hierarchy.complete;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.hierarchy.complete.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/214c95ca/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/TesterRule.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/TesterRule.java
 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/TesterRule.java
new file mode 100755
index 0000000..acc24cb
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/TesterRule.java
@@ -0,0 +1,96 @@
+/*
+ * 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.hierarchy.complete;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.mock.MockApplication;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+class TesterRule implements MethodRule
+{
+       private WebApplication application;
+       private WicketTester tester;
+
+       public TesterRule()
+       {
+       }
+
+       public WicketTester getTester()
+       {
+               return tester;
+       }
+
+       public void startPage(Page page)
+       {
+               tester.startPage(page);
+       }
+
+       public <T extends Page> void startPage(Class<T> page)
+       {
+               tester.startPage(page);
+       }
+
+
+       @Override
+       public Statement apply(final Statement base, FrameworkMethod method, 
Object target)
+       {
+               return new Statement()
+               {
+                       @Override
+                       public void evaluate() throws Throwable
+                       {
+                               WebApplication application = getApplication();
+                               if (application == null)
+                               {
+                                       application = new MockApplication();
+                               }
+                               tester = new WicketTester(application);
+                               tester.setExposeExceptions(true);
+                               try
+                               {
+                                       base.evaluate();
+                               }
+                               finally
+                               {
+                                       tester.destroy();
+                                       tester = null;
+                               }
+                       }
+               };
+       }
+
+       public WebApplication getApplication()
+       {
+               return application;
+       }
+
+       public TesterRule setApplication(WebApplication application)
+       {
+               this.application = application;
+               return this;
+       }
+
+       public Page getLastRenderedPage()
+       {
+               return tester.getLastRenderedPage();
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/214c95ca/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/WicketMatchers.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/WicketMatchers.java
 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/WicketMatchers.java
new file mode 100755
index 0000000..37d501e
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/hierarchy/complete/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.hierarchy.complete;
+
+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