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