You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by iv...@apache.org on 2014/02/10 09:25:15 UTC

git commit: WICKET-3335 initial pass on component queueing. borders and inlineenclosure not yet supported. pages, panels, and dequeuing into auto components works. see ComponentQueueingTest for details

Updated Branches:
  refs/heads/sandbox/component-queueing-2 [created] 02923940a


WICKET-3335 initial pass on component queueing. borders and inlineenclosure not yet supported. pages, panels, and dequeuing into auto components works. see ComponentQueueingTest for details


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

Branch: refs/heads/sandbox/component-queueing-2
Commit: 02923940a3a9df0dff70c2021895211fa36237c6
Parents: 99fcd91
Author: Igor Vaynberg <ig...@gmail.com>
Authored: Mon Feb 10 00:25:13 2014 -0800
Committer: Igor Vaynberg <ig...@gmail.com>
Committed: Mon Feb 10 00:25:13 2014 -0800

----------------------------------------------------------------------
 .../java/org/apache/wicket/ComponentQueue.java  | 147 ++++
 .../java/org/apache/wicket/IQueueRegion.java    |  34 +
 .../java/org/apache/wicket/MarkupContainer.java | 346 +++++++++-
 .../src/main/java/org/apache/wicket/Page.java   |   8 +-
 .../org/apache/wicket/markup/ComponentTag.java  |  35 +
 .../wicket/markup/html/internal/Enclosure.java  |  45 +-
 .../apache/wicket/markup/html/panel/Panel.java  |   4 +-
 .../markup/parser/filter/EnclosureHandler.java  |  27 +-
 .../ComponentQueueingPerformanceTest.java       | 395 +++++++++++
 .../wicket/queueing/ComponentQueueingTest.java  | 686 +++++++++++++++++++
 .../org/apache/wicket/queueing/HasPath.java     | 135 ++++
 .../org/apache/wicket/queueing/IsParentOf.java  |  98 +++
 .../java/org/apache/wicket/queueing/Path.java   | 101 +++
 .../apache/wicket/queueing/WicketMatchers.java  |  40 ++
 14 files changed, 2074 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/02923940/wicket-core/src/main/java/org/apache/wicket/ComponentQueue.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ComponentQueue.java b/wicket-core/src/main/java/org/apache/wicket/ComponentQueue.java
new file mode 100644
index 0000000..758970b
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/ComponentQueue.java
@@ -0,0 +1,147 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+
+/**
+ * Manages the component queue.
+ * 
+ * @author igor
+ */
+class ComponentQueue
+{
+	private static final int INITIAL = 8;
+	private static final int ADDITIONAL = 8;
+
+	private Component[] queue;
+	private int queueSize = 0;
+	private boolean dirty = false;
+	private String[] seen;
+	private int seenSize = 0;
+
+	void add(Component... components)
+	{
+		// TODO queueing this can be a more efficient implementation since we know the size of the
+		// array
+		for (Component component : components)
+		{
+			add(component);
+		}
+	}
+
+	void add(Component component)
+	{
+		if (component == null)
+		{
+			throw new IllegalArgumentException("component cannot be null");
+		}
+
+		if (seen != null)
+		{
+			for (int i = 0; i < seenSize; i++)
+			{
+				if (seen[i].equals(component.getId()))
+				{
+					throw new WicketRuntimeException("A component with id: " + component.getId()
+						+ " has already been queued");
+				}
+			}
+		}
+
+		if (dirty)
+		{
+			if (queueSize == 0)
+			{
+				queue = null;
+			}
+			else
+			{
+				Component[] replacement = new Component[queueSize + ADDITIONAL];
+				int pos = 0;
+				for (int i = 0; i < queue.length; i++)
+				{
+					if (queue[i] != null)
+					{
+						replacement[pos++] = queue[i];
+					}
+				}
+				queue = replacement;
+			}
+		}
+
+		if (queue == null)
+		{
+			queue = new Component[INITIAL];
+		}
+		else if (queue.length == queueSize)
+		{
+			queue = Arrays.copyOf(queue, queue.length + ADDITIONAL);
+		}
+		queue[queueSize] = component;
+		queueSize++;
+
+		if (seen == null)
+		{
+			seen = new String[INITIAL];
+		}
+		else if (seenSize == seen.length)
+		{
+			seen = Arrays.copyOf(seen, seen.length + ADDITIONAL);
+		}
+		seen[seenSize] = component.getId();
+		seenSize++;
+	}
+
+	Component remove(String id)
+	{
+		int seen = 0;
+		for (int i = 0; i < queue.length && seen < queueSize; i++)
+		{
+			Component component = queue[i];
+			if (component != null)
+			{
+				seen++;
+				if (component.getId().equals(id))
+				{
+					queue[i] = null;
+					dirty = true;
+					queueSize--;
+					return component;
+				}
+			}
+		}
+		return null;
+	}
+
+	public boolean isEmpty()
+	{
+		return queueSize == 0;
+	}
+
+	public Component get(String id)
+	{
+		for (Component component : queue)
+		{
+			if (component != null && component.getId().equals(id))
+			{
+				return component;
+			}
+		}
+		return null;
+	}
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/02923940/wicket-core/src/main/java/org/apache/wicket/IQueueRegion.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/IQueueRegion.java b/wicket-core/src/main/java/org/apache/wicket/IQueueRegion.java
new file mode 100644
index 0000000..86e30c6
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/IQueueRegion.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Demarcates components that can dequeue children. These are usually components with associated
+ * markup since the markup is needed to dequeue.
+ * 
+ * It is also important to note that components queued outside of a region cannot be dequeued into
+ * it since regions act as roots for the dequeue process because they contain the markup. As such,
+ * for example, a component queued in a page cannot be dequeued into a page child that is a panel
+ * because a panel is an {@link IQueueRegion}.
+ * 
+ * @author igor
+ * 
+ */
+public interface IQueueRegion
+{
+
+}

http://git-wip-us.apache.org/repos/asf/wicket/blob/02923940/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 5cae9bb..b537eca 100644
--- a/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java
+++ b/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java
@@ -36,11 +36,13 @@ 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;
 import org.apache.wicket.model.IWrapModel;
 import org.apache.wicket.settings.DebugSettings;
+import org.apache.wicket.util.collections.ArrayListStack;
 import org.apache.wicket.util.io.IClusterable;
 import org.apache.wicket.util.iterator.ComponentHierarchyIterator;
 import org.apache.wicket.util.lang.Args;
@@ -170,13 +172,22 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp
 				log.debug("Add " + child.getId() + " to " + this);
 			}
 
+			// remove child from existing parent
+			parent = child.getParent();
+			if (parent != null)
+			{
+				parent.remove(child);
+			}
+
 			// Add to map
-			addedComponent(child);
 			if (put(child) != null)
 			{
-				throw new IllegalArgumentException(exceptionMessage("A child with id '" +
-					child.getId() + "' already exists"));
+				throw new IllegalArgumentException(exceptionMessage("A child with id '"
+					+ child.getId() + "' already exists"));
 			}
+
+			addedComponent(child);
+
 		}
 		return this;
 	}
@@ -483,8 +494,8 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp
 		}
 
 		// Add to map
-		addedComponent(child);
 		put(child);
+		addedComponent(child);
 	}
 
 	/**
@@ -907,14 +918,37 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp
 		child.setParent(this);
 
 		final DebugSettings debugSettings = Application.get().getDebugSettings();
-		if (debugSettings.isLinePreciseReportingOnAddComponentEnabled() &&
-			debugSettings.getComponentUseCheck())
+		if (debugSettings.isLinePreciseReportingOnAddComponentEnabled()
+			&& debugSettings.getComponentUseCheck())
 		{
 			child.setMetaData(ADDED_AT_KEY,
 				ComponentStrings.toString(child, new MarkupException("added")));
 		}
 
-		final Page page = findPage();
+		Page page = null;
+		MarkupContainer queueRegion = null;
+		Component cursor = this;
+		while (cursor != null)
+		{
+			if (queueRegion == null && (cursor instanceof IQueueRegion))
+			{
+				queueRegion = (MarkupContainer)cursor;
+			}
+			if (cursor instanceof Page)
+			{
+				page = (Page)cursor;
+			}
+			cursor = cursor.getParent();
+		}
+
+		if (queueRegion != null && page != null)
+		{
+			if (!queueRegion.dequeuing)
+			{
+				queueRegion.dequeue();
+			}
+		}
+
 		if (page != null)
 		{
 			// tell the page a component has been added first, to allow it to initialize
@@ -1479,6 +1513,34 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp
 		renderComponentTagBody(markupStream, openTag);
 	}
 
+	@Override
+	protected void onInitialize()
+	{
+		super.onInitialize();
+		if (this instanceof IQueueRegion)
+		{
+			// dequeue auto components
+			Markup markup = getAssociatedMarkup();
+			if (markup != null)
+			{
+				// make sure we have markup, when running inside tests we wont
+				for (int i = 0; i < markup.size(); i++)
+				{
+					MarkupElement element = markup.get(i);
+					if (element instanceof ComponentTag)
+					{
+						ComponentTag tag = (ComponentTag)element;
+						if (tag.getAutoComponentFactory() != null)
+						{
+							Component auto = tag.getAutoComponentFactory().newComponent(tag);
+							queue(auto);
+						}
+					}
+				}
+			}
+		}
+	}
+
 	/**
 	 * @see org.apache.wicket.Component#onRender()
 	 */
@@ -1939,4 +2001,274 @@ public abstract class MarkupContainer extends Component implements Iterable<Comp
 		}
 	}
 
+	@Override
+	protected void onDetach()
+	{
+		super.onDetach();
+
+		if (queue != null && !queue.isEmpty())
+		{
+			throw new WicketRuntimeException("Detach called on component: " + getId()
+				+ " while it had a non-empty queue");
+		}
+		queue = null;
+	}
+
+	private transient ComponentQueue queue;
+
+	public void queue(Component... components)
+	{
+		if (queue == null)
+		{
+			queue = new ComponentQueue();
+		}
+		queue.add(components);
+
+		MarkupContainer region = null;
+		Page page = null;
+
+		MarkupContainer cursor = this;
+
+		while (cursor != null)
+		{
+			if (region == null && cursor instanceof IQueueRegion)
+			{
+				region = cursor;
+			}
+			if (cursor instanceof Page)
+			{
+				page = (Page)cursor;
+			}
+			cursor = cursor.getParent();
+		}
+
+		if (page != null)
+		{
+			region.dequeue();
+		}
+	}
+
+	ComponentQueue getQueue()
+	{
+		return queue;
+	}
+
+	private boolean dequeuing = false;
+
+	void dequeue()
+	{
+		if (!(this instanceof IQueueRegion))
+		{
+			// TODO queueing mesage
+			throw new UnsupportedOperationException();
+		}
+
+		if (dequeuing)
+		{
+			throw new IllegalStateException();
+		}
+
+		dequeuing = true;
+		try
+		{
+			internalDequeue();
+		}
+		finally
+		{
+			dequeuing = false;
+		}
+	}
+
+	private void internalDequeue()
+	{
+		class Repeater
+		{
+			int parentMarkupIndex;
+			Iterator<Component> renderIterator;
+			ComponentTag tag;
+
+			Repeater(int parentMarkupIndex, Iterator<Component> renderIterator, ComponentTag tag)
+			{
+				this.parentMarkupIndex = parentMarkupIndex;
+				this.renderIterator = renderIterator;
+				this.tag = tag;
+			}
+		}
+
+		Markup markup = getAssociatedMarkup();
+		if (markup == null)
+		{
+			// markup not found, skip dequeuing
+			// this sometimes happens when we are in a unit test
+			return;
+		}
+		// use arraydeque?
+		ArrayListStack<ComponentTag> tags = new ArrayListStack<ComponentTag>();
+		ArrayListStack<MarkupContainer> containers = new ArrayListStack<MarkupContainer>();
+		ArrayListStack<Repeater> repeaters = new ArrayListStack<Repeater>();
+
+		containers.push(this);
+
+		for (int i = 0; i < markup.size(); i++)
+		{
+			MarkupElement element = markup.get(i);
+
+			if (!(element instanceof ComponentTag))
+			{
+				continue;
+			}
+
+			ComponentTag tag = (ComponentTag)element;
+
+			if (tag instanceof WicketTag)
+			{
+				ComponentTag openTag = tag.getOpenTag() == null ? tag : tag.getOpenTag();
+				if (openTag.getAutoComponentFactory() == null)
+				{
+					// wicket tags that do not produce auto components can be ignored
+					continue;
+				}
+			}
+
+			if (tag.isClose())
+			{
+				ComponentTag closeTag = tags.pop();
+				containers.pop();
+
+				if (containers.peek() instanceof AbstractRepeater)
+				{
+					Repeater repeater = repeaters.peek();
+					if (repeater.renderIterator.hasNext())
+					{
+						containers.push((MarkupContainer)repeater.renderIterator.next());
+						tags.push(repeater.tag);
+						i = repeater.parentMarkupIndex;
+						continue;
+					}
+					else
+					{
+						// we rendered the last item, now time to close the repeater
+						repeaters.pop();
+						tags.pop();
+						containers.pop();
+					}
+				}
+			}
+			else
+			{
+				String id = tag.getId();
+				Component child = containers.peek().get(id);
+
+				// see if child is already added to parent
+				if (child == null)
+				{
+					// the container does not yet have a child with this id, see if we can
+					// dequeue
+					for (int j = containers.size() - 1; j >= 0; j--)
+					{
+						MarkupContainer container = containers.get(j);
+						child = container.getQueue() != null
+							? container.getQueue().remove(id)
+							: null;
+						if (child != null)
+						{
+							break;
+						}
+					}
+
+					if (child != null)
+					{
+						containers.peek().add(child);
+
+						if (child instanceof IQueueRegion)
+						{
+							((MarkupContainer)child).dequeue();
+						}
+
+					}
+				}
+				if (child == null)
+				{
+					// could not dequeue, skip until closing tag
+
+					if (tag.isOpen())
+					{
+						for (i = i + 1; i < markup.size(); i++)
+						{
+							MarkupElement e = markup.get(i);
+							if (e instanceof ComponentTag && ((ComponentTag)e).closes(tag))
+							{
+								break;
+							}
+						}
+					}
+
+					// if (dequeueSites == null)
+					// {
+					// dequeueSites = new HashSet<String>();
+					// }
+					// dequeueSites.add(id);
+
+				}
+				else
+				{
+					if (tag.isOpen())
+					{
+						if (child instanceof MarkupContainer)
+						{
+							containers.push((MarkupContainer)child);
+							tags.push(tag);
+
+							if (child instanceof AbstractRepeater)
+							{
+								Repeater repeater = new Repeater(i,
+									((AbstractRepeater)child).iterator(), tag);
+								if (repeater.renderIterator.hasNext())
+								{
+									repeaters.push(repeater);
+									containers
+										.push((MarkupContainer)repeater.renderIterator.next());
+									tags.push(tag);
+								}
+								else
+								{
+									// empty repeater, skip until closing tag
+									for (i = i + 1; i < markup.size(); i++)
+									{
+										MarkupElement e = markup.get(i);
+										if (e instanceof ComponentTag
+											&& ((ComponentTag)e).closes(tag))
+										{
+											break;
+										}
+									}
+									i--;
+									continue;
+								}
+							}
+						}
+						else
+						{
+							// web component, skip until closing tag
+							for (i = i + 1; i < markup.size(); i++)
+							{
+								MarkupElement e = markup.get(i);
+								if (e instanceof ComponentTag && ((ComponentTag)e).closes(tag))
+								{
+									break;
+								}
+							}
+						}
+					}
+					else
+					{
+						// openclose tag, nothing to do
+					}
+
+				}
+			}
+		}
+
+
+	}
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/02923940/wicket-core/src/main/java/org/apache/wicket/Page.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/Page.java b/wicket-core/src/main/java/org/apache/wicket/Page.java
index 70fd7b6..4e37bb2 100644
--- a/wicket-core/src/main/java/org/apache/wicket/Page.java
+++ b/wicket-core/src/main/java/org/apache/wicket/Page.java
@@ -41,6 +41,7 @@ import org.apache.wicket.util.lang.Generics;
 import org.apache.wicket.util.string.StringValue;
 import org.apache.wicket.util.visit.IVisit;
 import org.apache.wicket.util.visit.IVisitor;
+import org.apache.wicket.util.visit.Visits;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -81,7 +82,11 @@ import org.slf4j.LoggerFactory;
  * @author Johan Compagner
  * 
  */
-public abstract class Page extends MarkupContainer implements IRedirectListener, IRequestablePage
+public abstract class Page extends MarkupContainer
+	implements
+		IRedirectListener,
+		IRequestablePage,
+		IQueueRegion
 {
 	/** True if the page hierarchy has been modified in the current request. */
 	private static final int FLAG_IS_DIRTY = FLAG_RESERVED3;
@@ -1042,4 +1047,5 @@ public abstract class Page extends MarkupContainer implements IRedirectListener,
 	{
 		return renderedComponents != null && renderedComponents.contains(component);
 	}
+
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/02923940/wicket-core/src/main/java/org/apache/wicket/markup/ComponentTag.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/ComponentTag.java b/wicket-core/src/main/java/org/apache/wicket/markup/ComponentTag.java
index d9158f0..b572677 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/ComponentTag.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/ComponentTag.java
@@ -50,6 +50,25 @@ import org.slf4j.LoggerFactory;
  */
 public class ComponentTag extends MarkupElement
 {
+	/**
+	 * Factory that creates component during markup root container's initialization. These
+	 * components get queued, which allows other components to be dequeued under these auto
+	 * components.
+	 * 
+	 * TODO queueing javadoc improvements describing the process in more detail
+	 * 
+	 * @author igor
+	 * 
+	 */
+	public interface IAutoComponentFactory
+	{
+		/**
+		 * Creates a new instance of auto component to be queued
+		 */
+		Component newComponent(ComponentTag tag);
+	}
+
+
 	/** Log. */
 	private static final Logger log = LoggerFactory.getLogger(ComponentTag.class);
 
@@ -99,6 +118,8 @@ public class ComponentTag extends MarkupElement
 	/** Filters and Handlers may add their own attributes to the tag */
 	private Map<String, Object> userData;
 
+	private IAutoComponentFactory autoComponentFactory;
+
 	/**
 	 * Automatically create a XmlTag, assign the name and the type, and construct a ComponentTag
 	 * based on this XmlTag.
@@ -422,6 +443,8 @@ public class ComponentTag extends MarkupElement
 	{
 		dest.id = id;
 		dest.flags = flags;
+		dest.autoComponentFactory = autoComponentFactory;
+
 		if (markupClassRef != null)
 		{
 			dest.setMarkupClass(markupClassRef.get());
@@ -933,4 +956,16 @@ public class ComponentTag extends MarkupElement
 	public void onBeforeRender(final Component component, final MarkupStream markupStream)
 	{
 	}
+
+	public IAutoComponentFactory getAutoComponentFactory()
+	{
+		return autoComponentFactory;
+	}
+
+	public void setAutoComponentFactory(IAutoComponentFactory autoComponentFactory)
+	{
+		this.autoComponentFactory = autoComponentFactory;
+	}
+
+
 }

http://git-wip-us.apache.org/repos/asf/wicket/blob/02923940/wicket-core/src/main/java/org/apache/wicket/markup/html/internal/Enclosure.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/internal/Enclosure.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/internal/Enclosure.java
index 8a3c8e5..f2fcb49 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/html/internal/Enclosure.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/internal/Enclosure.java
@@ -29,8 +29,6 @@ import org.apache.wicket.markup.resolver.ComponentResolvers;
 import org.apache.wicket.markup.resolver.ComponentResolvers.ResolverFilter;
 import org.apache.wicket.markup.resolver.IComponentResolver;
 import org.apache.wicket.util.string.Strings;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 
 /**
@@ -80,6 +78,7 @@ import org.slf4j.LoggerFactory;
  * @see EnclosureHandler
  * @see EnclosureContainer
  * 
+ * @author igor
  * @author Juergen Donnerstag
  * @since 1.3
  */
@@ -87,13 +86,11 @@ public class Enclosure extends WebMarkupContainer implements IComponentResolver
 {
 	private static final long serialVersionUID = 1L;
 
-	private static final Logger log = LoggerFactory.getLogger(Enclosure.class);
-
 	/** The child component to delegate the isVisible() call to */
 	private Component childComponent;
 
 	/** Id of the child component that will control visibility of the enclosure */
-	private final CharSequence childId;
+	private final String childId;
 
 	/**
 	 * Construct.
@@ -101,7 +98,7 @@ public class Enclosure extends WebMarkupContainer implements IComponentResolver
 	 * @param id
 	 * @param childId
 	 */
-	public Enclosure(final String id, final CharSequence childId)
+	public Enclosure(final String id, final String childId)
 	{
 		super(id);
 
@@ -123,26 +120,36 @@ public class Enclosure extends WebMarkupContainer implements IComponentResolver
 		return childId.toString();
 	}
 
-	@Override
-	protected void onInitialize()
-	{
-		super.onInitialize();
-
-		// get Child Component. If not "added", ask a resolver to find it.
-		childComponent = getChildComponent(new MarkupStream(getMarkup()), getEnclosureParent());
-	}
-
 	protected final Component getChild()
 	{
+		if (childComponent == null)
+		{
+			// try to find child when queued
+			childComponent = get(childId);
+		}
+		if (childComponent == null)
+		{
+			// try to find child when resolved
+			childComponent = getChildComponent(new MarkupStream(getMarkup()), getEnclosureParent());
+		}
 		return childComponent;
 	}
 
 	@Override
 	public boolean isVisible()
 	{
-		return childComponent.determineVisibility() && super.isVisible();
+		return getChild().determineVisibility();
 	}
 
+
+	@Override
+	protected void onDetach()
+	{
+		super.onDetach();
+
+		// necessary when queued and lives with the page instead of just during render
+		childComponent = null;
+	}
 	/**
 	 * Get the real parent container
 	 * 
@@ -232,6 +239,12 @@ public class Enclosure extends WebMarkupContainer implements IComponentResolver
 	@Override
 	public Component resolve(MarkupContainer container, MarkupStream markupStream, ComponentTag tag)
 	{
+		// only resolved when auto, not when queued
+		// if (!isAuto())
+		// {
+		// return null;
+		// }
+
 		if (childId.equals(tag.getId()))
 		{
 			return childComponent;

http://git-wip-us.apache.org/repos/asf/wicket/blob/02923940/wicket-core/src/main/java/org/apache/wicket/markup/html/panel/Panel.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/html/panel/Panel.java b/wicket-core/src/main/java/org/apache/wicket/markup/html/panel/Panel.java
index 195eaee..0a2456b 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/html/panel/Panel.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/html/panel/Panel.java
@@ -16,8 +16,8 @@
  */
 package org.apache.wicket.markup.html.panel;
 
+import org.apache.wicket.IQueueRegion;
 import org.apache.wicket.markup.html.WebMarkupContainer;
-import org.apache.wicket.markup.parser.filter.WicketTagIdentifier;
 import org.apache.wicket.model.IModel;
 
 /**
@@ -50,7 +50,7 @@ import org.apache.wicket.model.IModel;
  * @author Jonathan Locke
  * @author Juergen Donnerstag
  */
-public abstract class Panel extends WebMarkupContainer
+public abstract class Panel extends WebMarkupContainer implements IQueueRegion
 {
 	private static final long serialVersionUID = 1L;
 

http://git-wip-us.apache.org/repos/asf/wicket/blob/02923940/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/EnclosureHandler.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/EnclosureHandler.java b/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/EnclosureHandler.java
index 25b1747..91d53e7 100644
--- a/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/EnclosureHandler.java
+++ b/wicket-core/src/main/java/org/apache/wicket/markup/parser/filter/EnclosureHandler.java
@@ -19,11 +19,12 @@ package org.apache.wicket.markup.parser.filter;
 import java.text.ParseException;
 import java.util.ArrayDeque;
 import java.util.Deque;
-import java.util.Stack;
+import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.ComponentTag.IAutoComponentFactory;
 import org.apache.wicket.markup.MarkupElement;
 import org.apache.wicket.markup.MarkupResourceStream;
 import org.apache.wicket.markup.MarkupStream;
@@ -53,6 +54,18 @@ public final class EnclosureHandler extends AbstractMarkupFilter implements ICom
 {
 	private static final long serialVersionUID = 1L;
 
+	private static final IAutoComponentFactory FACTORY = new IAutoComponentFactory()
+	{
+
+
+		@Override
+		public Component newComponent(ComponentTag tag)
+		{
+			return new Enclosure(tag.getId(), tag
+				.getAttribute(EnclosureHandler.CHILD_ATTRIBUTE));
+		}
+	};
+
 	/** */
 	public static final String ENCLOSURE = "enclosure";
 
@@ -62,6 +75,14 @@ public final class EnclosureHandler extends AbstractMarkupFilter implements ICom
 	/** Stack of <wicket:enclosure> tags */
 	private Deque<ComponentTag> stack;
 
+	/**
+	 * Used to assign unique ids to enclosures
+	 * 
+	 * TODO queueing: there has to be a better way of doing this, perhaps some merged-markup-unique
+	 * counter
+	 */
+	private static final AtomicLong index = new AtomicLong();
+
 	/** The id of the first wicket tag inside the enclosure */
 	private String childId;
 
@@ -90,6 +111,10 @@ public final class EnclosureHandler extends AbstractMarkupFilter implements ICom
 			// If open tag, than put the tag onto the stack
 			if (tag.isOpen())
 			{
+				tag.setId(tag.getId() + index.getAndIncrement());
+				tag.setModified(true);
+				tag.setAutoComponentFactory(FACTORY);
+
 				if (stack == null)
 				{
 					stack = new ArrayDeque<>();

http://git-wip-us.apache.org/repos/asf/wicket/blob/02923940/wicket-core/src/test/java/org/apache/wicket/queueing/ComponentQueueingPerformanceTest.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/test/java/org/apache/wicket/queueing/ComponentQueueingPerformanceTest.java b/wicket-core/src/test/java/org/apache/wicket/queueing/ComponentQueueingPerformanceTest.java
new file mode 100644
index 0000000..d3800b8
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/queueing/ComponentQueueingPerformanceTest.java
@@ -0,0 +1,395 @@
+/*
+ * 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.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.Page;
+import org.apache.wicket.WicketTestCase;
+import org.apache.wicket.markup.IMarkupResourceStreamProvider;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.RefreshingView;
+import org.apache.wicket.markup.repeater.util.ModelIteratorAdapter;
+import org.apache.wicket.mock.MockApplication;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.StringResourceStream;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.Test;
+
+public class ComponentQueueingPerformanceTest extends WicketTestCase
+{
+	private void run(Class<? extends Page> pageClass)
+	{
+		WicketTester tester = new WicketTester(new MockApplication());
+		tester.startPage(pageClass);
+		tester.destroy();
+	}
+
+	// @Test
+	public void performance()
+	{
+		final int warmup = 30;
+		final int performance = 60;
+
+		tester.startPage(AddContactsPage.class);
+
+		for (int i = 0; i < warmup; i++)
+		{
+			run(AddContactsPage.class);
+		}
+		long start = System.currentTimeMillis();
+		for (int i = 0; i < performance; i++)
+		{
+			run(AddContactsPage.class);
+		}
+		long end = System.currentTimeMillis();
+		long addDuration = end - start;
+
+		for (int i = 0; i < warmup; i++)
+		{
+			run(QueueContactsPage.class);
+		}
+		start = System.currentTimeMillis();
+		for (int i = 0; i < performance; i++)
+		{
+			run(QueueContactsPage.class);
+		}
+		end = System.currentTimeMillis();
+		long queueDuration = end - start;
+
+
+		System.out.println("add duration: " + addDuration + " queue duration: " + queueDuration);
+
+	}
+
+
+	@Test
+	public void consistency()
+	{
+		tester.startPage(new QueueContactsPage());
+		String queue = tester.getLastResponseAsString();
+		tester.startPage(new AddContactsPage());
+		String add = tester.getLastResponseAsString();
+		assertEquals(queue, add);
+	}
+
+	private static class PhoneNumber
+	{
+		String id = UUID.randomUUID().toString();
+		String areacode = "234";
+		String prefix = "342";
+		String suffix = "3423";
+	}
+
+	private static class Address
+	{
+		String id = UUID.randomUUID().toString();
+		String street = "2343 Jsdfjsf St.";
+		String city = "Ksdfjsfs";
+		String state = "AS";
+		String zipcode = "32434";
+	}
+
+	private static class Contact
+	{
+		String id = UUID.randomUUID().toString();
+		String first = "Jlkjsf";
+		String last = "Kjwieojkjf";
+		Address address = new Address();
+		PhoneNumber work = new PhoneNumber();
+		PhoneNumber cell = new PhoneNumber();
+
+	}
+
+	static Store store = new Store();
+
+	private static class Store
+	{
+		Map<String, PhoneNumber> phones = new HashMap<String, PhoneNumber>();
+		Map<String, Address> addresses = new HashMap<String, Address>();
+		Map<String, Contact> contacts = new HashMap<String, Contact>();
+
+		public <T> T get(Class<T> clazz, String id)
+		{
+			if (PhoneNumber.class.equals(clazz))
+			{
+				return (T)phones.get(id);
+			}
+			else if (Address.class.equals(clazz))
+			{
+				return (T)addresses.get(id);
+			}
+			else if (Contact.class.equals(clazz))
+			{
+				return (T)contacts.get(id);
+			}
+			throw new RuntimeException();
+		}
+
+		public Store()
+		{
+			for (int i = 0; i < 250; i++)
+			{
+				Contact contact = new Contact();
+				contacts.put(contact.id, contact);
+			}
+		}
+
+	}
+
+	private static class ContactModel extends LoadableDetachableModel<Contact>
+	{
+		private String id;
+
+		public ContactModel(Contact contact)
+		{
+			super(contact);
+			this.id = contact.id;
+		}
+
+		@Override
+		protected Contact load()
+		{
+			return store.contacts.get(id);
+		}
+
+	}
+
+	private static abstract class AbstractPhonePanel extends TestPanel
+	{
+		public AbstractPhonePanel(String id, IModel<PhoneNumber> phone)
+		{
+			super(id);
+			setPanelMarkup("<wicket:panel><span wicket:id='areacode'></span> <span wicket:id='prefix'></span>-<span wicket:id='suffix'></span></wicket:panel>");
+		}
+	}
+
+	private static class AddPhonePanel extends AbstractPhonePanel
+	{
+		public AddPhonePanel(String id, IModel<PhoneNumber> phone)
+		{
+			super(id, phone);
+			add(new Label("areacode", new PropertyModel(phone, "areacode")));
+			add(new Label("prefix", new PropertyModel(phone, "prefix")));
+			add(new Label("suffix", new PropertyModel(phone, "suffix")));
+		}
+	}
+	private static class QueuePhonePanel extends AbstractPhonePanel
+	{
+		public QueuePhonePanel(String id, IModel<PhoneNumber> phone)
+		{
+			super(id, phone);
+			queue(new Label("areacode", new PropertyModel(phone, "areacode")));
+			queue(new Label("prefix", new PropertyModel(phone, "prefix")));
+			queue(new Label("suffix", new PropertyModel(phone, "suffix")));
+		}
+	}
+
+	private static abstract class AbstractAddressPanel extends TestPanel
+	{
+		public AbstractAddressPanel(String id, IModel<Address> addr)
+		{
+			super(id);
+			setPanelMarkup("<wicket:panel><span wicket:id='street'></span><br/><span wicket:id='city'></span>, <span wicket:id='state'></span> <span wicket:id='zipcode'></span></wicket:panel>");
+		}
+	}
+
+	private static class AddAddressPanel extends AbstractAddressPanel
+	{
+		public AddAddressPanel(String id, IModel<Address> addr)
+		{
+			super(id, addr);
+			add(new Label("street", new PropertyModel(addr, "street")));
+			add(new Label("city", new PropertyModel(addr, "city")));
+			add(new Label("state", new PropertyModel(addr, "state")));
+			add(new Label("zipcode", new PropertyModel(addr, "zipcode")));
+		}
+	}
+	private class QueueAddressPanel extends AbstractAddressPanel
+	{
+		public QueueAddressPanel(String id, IModel<Address> addr)
+		{
+			super(id, addr);
+			queue(new Label("street", new PropertyModel(addr, "street")));
+			queue(new Label("city", new PropertyModel(addr, "city")));
+			queue(new Label("sate", new PropertyModel(addr, "state")));
+			queue(new Label("zipcode", new PropertyModel(addr, "zipcode")));
+		}
+	}
+
+	static class AbstractContactsPage extends TestPage
+	{
+		AbstractContactsPage()
+		{
+			// @formatter:off
+			setPageMarkup(
+				"  <div wicket:id='contacts'>"
+				+ "  <span wicket:id='first'></span>"
+				+ "  <span wicket:id='last'></span>"
+				+ "  <div wicket:id='addr'></div>"
+				+ "  <div wicket:id='work'></div>"
+				+ "  <div wicket:id='cell'></div>"
+				+ "</div>");
+			// @formatter:on
+
+		}
+	}
+
+	public static class AddContactsPage extends AbstractContactsPage
+	{
+		public AddContactsPage()
+		{
+			add(new RefreshingView<Contact>("contacts")
+			{
+				@Override
+				protected Iterator<IModel<Contact>> getItemModels()
+				{
+					return new ModelIteratorAdapter<Contact>(store.contacts.values())
+					{
+						@Override
+						protected IModel<Contact> model(Contact object)
+						{
+							return new ContactModel(object);
+						}
+					};
+				}
+
+				@Override
+				protected void populateItem(Item<Contact> item)
+				{
+					IModel<Contact> model = item.getModel();
+					item.add(new Label("first", new PropertyModel(model, "first")));
+					item.add(new Label("last", new PropertyModel(model, "first")));
+					item.add(new AddAddressPanel("addr", new PropertyModel(model, "address")));
+					item.add(new AddPhonePanel("work", new PropertyModel(model, "work")));
+					item.add(new AddPhonePanel("cell", new PropertyModel(model, "cell")));
+				}
+			});
+
+		}
+	}
+
+	public static class QueueContactsPage extends AbstractContactsPage
+	{
+		public QueueContactsPage()
+		{
+			queue(new RefreshingView<Contact>("contacts")
+			{
+				@Override
+				protected Iterator<IModel<Contact>> getItemModels()
+				{
+					return new ModelIteratorAdapter<Contact>(store.contacts.values())
+					{
+						@Override
+						protected IModel<Contact> model(Contact object)
+						{
+							return new ContactModel(object);
+						}
+					};
+				}
+
+				@Override
+				protected void populateItem(Item<Contact> item)
+				{
+					IModel<Contact> model = item.getModel();
+					item.queue(new Label("first", new PropertyModel(model, "first")));
+					item.queue(new Label("last", new PropertyModel(model, "first")));
+					item.queue(new AddAddressPanel("addr", new PropertyModel(model, "address")));
+					item.queue(new AddPhonePanel("work", new PropertyModel(model, "work")));
+					item.queue(new AddPhonePanel("cell", new PropertyModel(model, "cell")));
+				}
+			});
+
+		}
+	}
+
+	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/02923940/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 100644
index 0000000..cda2e30
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/queueing/ComponentQueueingTest.java
@@ -0,0 +1,686 @@
+/*
+ * 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.internal.Enclosure;
+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(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(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(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(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(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,b,c] -> [a[b,c]] } */
+	@Test
+	public void dequeue7()
+	{
+		TestPage p = new TestPage();
+
+		// @formatter:off
+		p.setPageMarkup(
+			"  <p wicket:id='a'>"
+			+ "  <p wicket:id='b'>"
+			+ "    <p wicket:id='c'></p>"
+			+ "    <p wicket:id='d'>"
+			+ "      <p wicket:id='e'></p>"
+			+ "    </p>"
+			+ "    <p wicket:id='f'>"
+			+ "      <p wicket:id='g'></p>"
+			+ "    </p>"
+			+ "  </p>"
+			+ "</p>");
+		// @formatter:on
+
+		MarkupContainer a = new A(), b = new B(), c = new C(), d = new D(), e = new E(), f = new F(), g = new G();
+
+		a.add(b);
+		b.queue(e);
+		p.queue(a, c);
+		b.add(d);
+		f.queue(g);
+		b.add(f);
+
+		tester.startPage(p);
+
+		assertThat(p, hasPath(new Path(a, b, c)));
+		assertThat(p, hasPath(new Path(a, b, d, e)));
+		assertThat(p, hasPath(new Path(a, b, f, g)));
+	}
+
+
+	/** {@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)));
+	}
+
+	@Test
+	public void dequeueWithRepeater1()
+	{
+		TestPage p = new TestPage();
+		p.setPageMarkup("<p wicket:id='lv'><p wicket:id='b'><p wicket:id='c'></p></p></p>");
+
+		LV l = new LV(3)
+		{
+			@Override
+			protected void populateItem(ListItem<Integer> item)
+			{
+				item.queue(new B(), new C());
+			}
+		};
+
+		p.queue(l);
+
+		tester.startPage(p);
+
+		assertThat(l.size(), is(3));
+		for (Component item : l)
+		{
+			assertThat(p, hasPath(new Path(l, item, new B(), new C())));
+		}
+	}
+
+	/** repeater */
+	@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='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 dequeueWithRepeater3()
+	{
+		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
+		}
+	}
+
+
+	@Test
+	public void autos1()
+	{
+		TestPage p = new TestPage();
+		p.setPageMarkup("<wicket:enclosure child='a'><div wicket:id='a'></div><div wicket:id='b'></div></wicket:enclosure>");
+		A a = new A();
+		B b = new B();
+		p.queue(a, b);
+		tester.startPage(p);
+
+		assertTrue(a.getParent() instanceof Enclosure);
+		assertTrue(b.getParent() instanceof Enclosure);
+
+		// A is visible, enclosure renders
+
+		assertEquals(
+			"<wicket:enclosure child=\"a\"><div wicket:id=\"a\"></div><div wicket:id=\"b\"></div></wicket:enclosure>",
+			tester.getLastResponseAsString());
+
+		// A is not visible, enclosure does not render
+
+		a.setVisible(false);
+		tester.startPage(p);
+		assertEquals("", tester.getLastResponseAsString());
+	}
+
+	@Test
+	public void autos2()
+	{
+		TestPage p = new TestPage();
+		p.setPageMarkup("<wicket:enclosure child='a'><div wicket:id='a'></div><div wicket:id='b'></div></wicket:enclosure>");
+		A a = new A();
+		B b = new B();
+		p.add(a, b);
+		tester.startPage(p);
+
+		assertTrue(a.getParent() instanceof TestPage);
+		assertTrue(b.getParent() instanceof TestPage);
+
+		// A is visible, enclosure renders
+
+		assertEquals(
+			"<wicket:enclosure child=\"a\"><div wicket:id=\"a\"></div><div wicket:id=\"b\"></div></wicket:enclosure>",
+			tester.getLastResponseAsString());
+
+		// A is not visible, enclosure does not render
+
+		a.setVisible(false);
+		tester.startPage(p);
+		assertEquals("", tester.getLastResponseAsString());
+	}
+
+	@Test
+	public void autos3()
+	{
+		TestPage p = new TestPage();
+		p.setPageMarkup("<wicket:enclosure child='a'><div wicket:id='a'></div><div wicket:id='b'></div></wicket:enclosure>");
+		A a = new A();
+		B b = new B();
+		p.queue(b);
+		p.add(a);
+		tester.startPage(p);
+
+		assertTrue(a.getParent() instanceof TestPage);
+		assertTrue(b.getParent() instanceof Enclosure);
+
+		// A is visible, enclosure renders
+
+		assertEquals(
+			"<wicket:enclosure child=\"a\"><div wicket:id=\"a\"></div><div wicket:id=\"b\"></div></wicket:enclosure>",
+			tester.getLastResponseAsString());
+
+		// A is not visible, enclosure does not render
+
+		a.setVisible(false);
+		tester.startPage(p);
+		assertEquals("", tester.getLastResponseAsString());
+	}
+
+	@Test
+	public void autos4()
+	{
+		TestPage p = new TestPage();
+		p.setPageMarkup("<wicket:enclosure child='a'><div wicket:id='a'></div><div wicket:id='b'></div></wicket:enclosure>");
+		A a = new A();
+		B b = new B();
+		p.add(b);
+		p.queue(a);
+		tester.startPage(p);
+
+		assertTrue(a.getParent() instanceof Enclosure);
+		assertTrue(b.getParent() instanceof TestPage);
+
+		// A is visible, enclosure renders
+
+		assertEquals(
+			"<wicket:enclosure child=\"a\"><div wicket:id=\"a\"></div><div wicket:id=\"b\"></div></wicket:enclosure>",
+			tester.getLastResponseAsString());
+
+		// A is not visible, enclosure does not render
+
+		a.setVisible(false);
+		tester.startPage(p);
+		assertEquals("", tester.getLastResponseAsString());
+	}
+
+	@Test
+	public void autos5()
+	{
+		TestPage p = new TestPage();
+		p.setPageMarkup("<wicket:enclosure child='a'><div wicket:id='a'></div><div wicket:id='b'></div></wicket:enclosure>");
+		A a = new A();
+		B b = new B();
+		p.queue(a);
+		p.add(b);
+		tester.startPage(p);
+
+		assertTrue(a.getParent() instanceof Enclosure);
+		assertTrue(b.getParent() instanceof TestPage);
+
+
+		// A is visible, enclosure renders
+
+		assertEquals(
+			"<wicket:enclosure child=\"a\"><div wicket:id=\"a\"></div><div wicket:id=\"b\"></div></wicket:enclosure>",
+			tester.getLastResponseAsString());
+
+		// A is not visible, enclosure does not render
+
+		a.setVisible(false);
+		tester.startPage(p);
+		assertEquals("", tester.getLastResponseAsString());
+	}
+
+	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 D extends WebMarkupContainer
+	{
+		public D()
+		{
+			super("d");
+		}
+	}
+
+	private static class E extends WebMarkupContainer
+	{
+		public E()
+		{
+			super("e");
+		}
+	}
+
+	private static class F extends WebMarkupContainer
+	{
+		public F()
+		{
+			super("f");
+		}
+	}
+	private static class G extends WebMarkupContainer
+	{
+		public G()
+		{
+			super("g");
+		}
+	}
+
+	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/02923940/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 100644
index 0000000..3095095
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/queueing/HasPath.java
@@ -0,0 +1,135 @@
+/*
+ * 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.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/02923940/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 100644
index 0000000..3f6ead0
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/queueing/IsParentOf.java
@@ -0,0 +1,98 @@
+/*
+ * 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.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/02923940/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 100644
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/02923940/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 100644
index 0000000..0ec0bf3
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/queueing/WicketMatchers.java
@@ -0,0 +1,40 @@
+/*
+ * 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);
+	}
+
+	public static <T> Matcher<Component> hasPath(Component... path)
+	{
+		return new HasPath(new Path(path));
+	}
+
+}
\ No newline at end of file