You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by sv...@apache.org on 2012/02/05 23:02:55 UTC

[5/9] moving old tree examples away, merged recursive panel example into panel compref

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/NodeBorder.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/NodeBorder.java
index 0000000,0000000..fe566a4
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/NodeBorder.java
@@@ -1,0 -1,0 +1,85 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.table;
++
++import org.apache.wicket.Component;
++import org.apache.wicket.behavior.Behavior;
++import org.apache.wicket.markup.ComponentTag;
++import org.apache.wicket.markup.repeater.Item;
++import org.apache.wicket.model.IModel;
++import org.apache.wicket.request.Response;
++
++/**
++ * A border for a node component which renders nested <code>DIV</code>s to simulate the structure of
++ * parental branches inside a tabular layout.
++ * 
++ * @see NodeModel
++ * @see TreeColumn#populateItem(Item, String, IModel)
++ * @author svenmeier
++ */
++public class NodeBorder extends Behavior
++{
++
++	private static final long serialVersionUID = 1L;
++
++	private boolean[] branches;
++
++	public NodeBorder(boolean[] branches)
++	{
++		this.branches = branches;
++	}
++
++	@Override
++	public void beforeRender(Component component)
++	{
++		Response response = component.getResponse();
++
++		for (int i = 0; i < branches.length; i++)
++		{
++			if (i > 0)
++			{
++				response.write("<div class=\"tree-subtree\">");
++			}
++
++			if (branches[i])
++			{
++				response.write("<div class=\"tree-branch tree-branch-mid\">");
++			}
++			else
++			{
++				response.write("<div class=\"tree-branch tree-branch-last\">");
++			}
++		}
++	}
++
++	@Override
++	public void onComponentTag(Component component, ComponentTag tag)
++	{
++		tag.put("class", "tree-node");
++	}
++
++	@Override
++	public void afterRender(Component component)
++	{
++		Response response = component.getResponse();
++
++		for (int i = 0; i < branches.length; i++)
++		{
++			response.write("</div>");
++		}
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/NodeModel.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/NodeModel.java
index 0000000,0000000..b2bf466
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/NodeModel.java
@@@ -1,0 -1,0 +1,96 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements.  See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License.  You may obtain a copy of the License at
++ *
++ *      http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.wicket.extensions.markup.html.repeater.tree.table;
++
++import java.util.Arrays;
++
++import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider;
++import org.apache.wicket.model.IModel;
++
++/**
++ * A model wrapping the actual node model, carrying additional information about the parental
++ * branches.
++ * 
++ * @see #getBranches()
++ * @see ITreeProvider#model(Object)
++ * 
++ * @author svenmeier
++ */
++public class NodeModel<T> implements IModel<T>
++{
++
++	private static final long serialVersionUID = 1L;
++
++	private IModel<T> model;
++
++	private boolean[] branches;
++
++	public NodeModel(IModel<T> model, boolean[] branches)
++	{
++		this.model = model;
++		this.branches = branches;
++	}
++
++	public IModel<T> getWrappedModel()
++	{
++		return model;
++	}
++
++	public T getObject()
++	{
++		return model.getObject();
++	}
++
++	public void setObject(T object)
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public void detach()
++	{
++		model.detach();
++	}
++
++	public int getDepth()
++	{
++		return branches.length;
++	}
++
++	public boolean[] getBranches()
++	{
++		return branches;
++	}
++
++	@Override
++	public int hashCode()
++	{
++		return model.hashCode();
++	}
++
++	@Override
++	public boolean equals(Object obj)
++	{
++		if (obj instanceof NodeModel<?>)
++		{
++			NodeModel<?> nodeModel = (NodeModel<?>)obj;
++
++			return Arrays.equals(this.branches, nodeModel.branches) &&
++				this.model.equals((nodeModel).model);
++		}
++		return false;
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/TreeColumn.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/TreeColumn.java
index 0000000,0000000..4f086ec
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/TreeColumn.java
@@@ -1,0 -1,0 +1,61 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.table;
++
++import org.apache.wicket.Component;
++import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
++import org.apache.wicket.markup.repeater.Item;
++import org.apache.wicket.model.IModel;
++
++/**
++ * @author svenmeier
++ */
++public class TreeColumn<T> extends AbstractTreeColumn<T>
++{
++
++	private static final long serialVersionUID = 1L;
++
++	public TreeColumn(IModel<String> displayModel)
++	{
++		super(displayModel);
++	}
++
++	public TreeColumn(IModel<String> displayModel, String sortProperty)
++	{
++		super(displayModel, sortProperty);
++	}
++
++	@Override
++	public String getCssClass()
++	{
++		return "tree";
++	}
++
++	public void populateItem(Item<ICellPopulator<T>> cellItem, String componentId,
++		IModel<T> rowModel)
++	{
++
++		NodeModel<T> nodeModel = (NodeModel<T>)rowModel;
++
++		Component nodeComponent = getTree().newNodeComponent(componentId,
++			nodeModel.getWrappedModel());
++
++		nodeComponent.add(new NodeBorder(nodeModel.getBranches()));
++
++		cellItem.add(nodeComponent);
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/TreeDataProvider.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/TreeDataProvider.java
index 0000000,0000000..6749a4b
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/TreeDataProvider.java
@@@ -1,0 -1,0 +1,194 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.table;
++
++import java.util.Iterator;
++
++import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider;
++import org.apache.wicket.markup.repeater.data.IDataProvider;
++import org.apache.wicket.model.IModel;
++
++/**
++ * An adapter of a {@link ITreeProvider} to a {@link IDataProvider}.
++ * 
++ * @author svenmeier
++ */
++public abstract class TreeDataProvider<T> implements ITreeDataProvider<T>
++{
++	private static final long serialVersionUID = 1L;
++
++	private final ITreeProvider<T> provider;
++
++	private Branch currentBranch;
++
++	private Branch previousBranch;
++
++	private int size = -1;
++
++	public TreeDataProvider(ITreeProvider<T> provider)
++	{
++		this.provider = provider;
++	}
++
++	public long size()
++	{
++		if (size == -1)
++		{
++			size = 0;
++
++			Iterator<? extends T> iterator = iterator(0, Integer.MAX_VALUE);
++			while (iterator.hasNext())
++			{
++				iterator.next();
++
++				size++;
++			}
++		}
++		return size;
++	}
++
++	public Iterator<? extends T> iterator(long first, long count)
++	{
++		currentBranch = new Branch(null, provider.getRoots());
++
++		Iterator<T> iterator = new Iterator<T>()
++		{
++
++			public boolean hasNext()
++			{
++				while (currentBranch != null)
++				{
++					if (currentBranch.hasNext())
++					{
++						return true;
++					}
++					currentBranch = currentBranch.parent;
++				}
++
++				return false;
++			}
++
++			public T next()
++			{
++				if (!hasNext())
++				{
++					throw new IllegalStateException();
++				}
++
++				T next = currentBranch.next();
++
++				previousBranch = currentBranch;
++
++				if (iterateChildren(next))
++				{
++					currentBranch = new Branch(previousBranch, provider.getChildren(next));
++				}
++
++				return next;
++			}
++
++			public void remove()
++			{
++				throw new UnsupportedOperationException();
++			}
++		};
++
++		for (int i = 0; i < first; i++)
++		{
++			iterator.next();
++		}
++
++		return iterator;
++	}
++
++	/**
++	 * Hook method to decide wether the given node's children should be iterated.
++	 */
++	protected abstract boolean iterateChildren(T object);
++
++	public NodeModel<T> model(T object)
++	{
++		return previousBranch.wrapModel(provider.model(object));
++	}
++
++	public void detach()
++	{
++		currentBranch = null;
++		previousBranch = null;
++		size = -1;
++	}
++
++	private class Branch implements Iterator<T>
++	{
++		private Branch parent;
++
++		private Iterator<? extends T> children;
++
++		public Branch(Branch parent, Iterator<? extends T> children)
++		{
++			this.parent = parent;
++			this.children = children;
++		}
++
++		public NodeModel<T> wrapModel(IModel<T> model)
++		{
++			boolean[] branches = new boolean[getDepth()];
++
++			Branch branch = this;
++			for (int c = branches.length - 1; c >= 0; c--)
++			{
++				branches[c] = branch.hasNext();
++
++				branch = branch.parent;
++			}
++
++			return new NodeModel<T>(model, branches);
++		}
++
++		public int getDepth()
++		{
++			if (parent == null)
++			{
++				return 1;
++			}
++			else
++			{
++				return parent.getDepth() + 1;
++			}
++		}
++
++		public boolean hasNext()
++		{
++			return children.hasNext();
++		}
++
++		public T next()
++		{
++			if (!hasNext())
++			{
++				throw new IllegalStateException();
++			}
++
++			return children.next();
++		}
++
++		public void remove()
++		{
++			throw new UnsupportedOperationException();
++		}
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/HumanTheme.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/HumanTheme.java
index 0000000,0000000..70efe17
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/HumanTheme.java
@@@ -1,0 -1,0 +1,48 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.theme;
++
++import org.apache.wicket.Component;
++import org.apache.wicket.behavior.Behavior;
++import org.apache.wicket.markup.ComponentTag;
++import org.apache.wicket.markup.head.CssHeaderItem;
++import org.apache.wicket.markup.head.IHeaderResponse;
++import org.apache.wicket.request.resource.PackageResourceReference;
++import org.apache.wicket.request.resource.ResourceReference;
++
++/**
++ * @author svenmeier
++ */
++public class HumanTheme extends Behavior
++{
++	private static final long serialVersionUID = 1L;
++
++	private static final ResourceReference CSS = new PackageResourceReference(WindowsTheme.class,
++		"human/theme.css");
++
++	@Override
++	public void onComponentTag(Component component, ComponentTag tag)
++	{
++		tag.append("class", "tree-theme-human", " ");
++	}
++
++	@Override
++	public void renderHead(Component component, IHeaderResponse response)
++	{
++		response.render(CssHeaderItem.forReference(CSS));
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/WindowsTheme.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/WindowsTheme.java
index 0000000,0000000..29c8a0d
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/WindowsTheme.java
@@@ -1,0 -1,0 +1,48 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.theme;
++
++import org.apache.wicket.Component;
++import org.apache.wicket.behavior.Behavior;
++import org.apache.wicket.markup.ComponentTag;
++import org.apache.wicket.markup.head.CssHeaderItem;
++import org.apache.wicket.markup.head.IHeaderResponse;
++import org.apache.wicket.request.resource.PackageResourceReference;
++import org.apache.wicket.request.resource.ResourceReference;
++
++/**
++ * @author svenmeier
++ */
++public class WindowsTheme extends Behavior
++{
++	private static final long serialVersionUID = 1L;
++
++	private static final ResourceReference CSS = new PackageResourceReference(WindowsTheme.class,
++		"windows/theme.css");
++
++	@Override
++	public void onComponentTag(Component component, ComponentTag tag)
++	{
++		tag.append("class", "tree-theme-windows", " ");
++	}
++
++	@Override
++	public void renderHead(Component component, IHeaderResponse response)
++	{
++		response.render(CssHeaderItem.forReference(CSS));
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/folder-other.gif
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/folder-other.gif
index 0000000,0000000..a264baa
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/folder-other.gif
@@@ -1,0 -1,0 +1,5 @@@
++GIF89a
++
++A�E�xԘ"�C�7iΐ�&�Anؓf��5f�
++R�C��h[���'BA	z�L�B�J�*Y�8(�@O5lƁ�K�!F��1e"�飠�=hx���H�4k� (���5lڴq��
�>
++���9t�����@Aq���s�b���T �|�

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/folder.gif
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/folder.gif
index 0000000,0000000..55acec1
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/folder.gif
@@@ -1,0 -1,0 +1,5 @@@
++GIF89a
++
++%`�ء�mX
++
++

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/theme.css
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/theme.css
index 0000000,0000000..4cc221c
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/theme.css
@@@ -1,0 -1,0 +1,126 @@@
++/*
++ * 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.
++ */
++
++.tree-theme-human div.tree-subtree {
++  margin: 0px 0px 0px 18px;
++  padding: 0px;
++}
++
++.tree-theme-human div.tree-branch {
++  margin: 0px;
++  padding: 0px;
++}
++
++.tree-theme-human div.tree-branch-mid {
++}
++
++.tree-theme-human div.tree-branch-last {
++}
++
++/* node */
++
++.tree-theme-human span.tree-junction {
++  display: block;
++  float: left;
++  width: 18px;
++}
++
++.tree-theme-human a.tree-junction-expanded {
++  display: block;
++  float: left;
++  width: 18px;
++  background-image: url(tree.gif);
++  background-position: -18px center;
++  background-repeat: no-repeat;
++  text-decoration: none;
++}
++
++.tree-theme-human a.tree-junction-expanded:hover {
++  background-position: -54px center;
++}
++
++.tree-theme-human a.tree-junction-collapsed {
++  display: block;
++  float: left;
++  width: 18px;
++  background-image: url(tree.gif);
++  background-position: 0px center;
++  background-repeat: no-repeat;
++  text-decoration: none;
++}
++
++.tree-theme-human a.tree-junction-collapsed:hover {
++  background-position: -36px center;
++}
++
++.tree-theme-human span.tree-content {
++  display: block;
++  margin-left: 18px;
++}
++
++/* tabletree */
++
++.tree-theme-human table {
++  border-spacing: 0px;
++  border-collapse: collapse;
++}
++
++.tree-theme-human tr.selected {
++  background-color: #fad184;
++}
++
++.tree-theme-human td {
++  /* allow tree divs to fill complete td (regretfully height 100% on divs does not work) */
++  margin: 0px;
++  padding: 0px;
++}
++
++/* content */
++
++.tree-theme-human span.tree-content a {
++  color: black;
++  text-decoration: none;
++}
++
++.tree-theme-human span.tree-content a:hover {
++  text-decoration: underline;
++}
++
++.tree-theme-human span.tree-content .selected {
++  background-color: #fad184;
++}
++
++.tree-theme-human .tree-folder-closed {
++  padding-left: 18px;
++  background-image: url(folder.gif);
++  background-position: 0px 50%;
++  background-repeat: no-repeat;
++}
++
++.tree-theme-human .tree-folder-open {
++  padding-left: 18px;
++  background-image: url(folder.gif);
++  background-position: 0px 50%;
++  background-repeat: no-repeat;
++}
++
++.tree-theme-human .tree-folder-other {
++  padding-left: 18px;
++  background-image: url(folder-other.gif);
++  background-position: 0px 50%;
++  background-repeat: no-repeat;
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/tree.gif
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/tree.gif
index 0000000,0000000..ba6f173
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/human/tree.gif
@@@ -1,0 -1,0 +1,2 @@@
++GIF89aH
++

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/folder-closed.gif
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/folder-closed.gif
index 0000000,0000000..eb12976
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/folder-closed.gif
@@@ -1,0 -1,0 +1,2 @@@
++GIF89a
++-$���-|Bg "	C

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/folder-open.gif
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/folder-open.gif
index 0000000,0000000..c5c3110
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/folder-open.gif
@@@ -1,0 -1,0 +1,4 @@@
++GIF89a
++������׸@ڿl�넙g������Pɠ!��p�r�ׄ��"�qȕ0��Ƭz����u����붶����ƚ���������
++�
++ D/���E

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/folder-other.gif
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/folder-other.gif
index 0000000,0000000..42d7318
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/folder-other.gif
@@@ -1,0 -1,0 +1,2 @@@
++GIF89a
++�>>�73ʄ�<��7 #Ճ��?788߂&����2�A-�7�+���B��ף���*@`�Ç	0 ���/vB��P 

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/theme.css
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/theme.css
index 0000000,0000000..12bc3c9
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/theme.css
@@@ -1,0 -1,0 +1,126 @@@
++/*
++ * 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.
++ */
++
++.tree-theme-windows div.tree-subtree {
++  margin: 0px 0px 0px 18px;
++  padding: 0px;
++}
++
++.tree-theme-windows div.tree-branch {
++  margin: 0px;
++  padding: 0px;
++}
++
++.tree-theme-windows div.tree-branch-mid {
++  background-image: url(tree.gif);
++  background-position: -54px center;
++  background-repeat: repeat-y;
++}
++
++.tree-theme-windows div.tree-branch-last {
++}
++
++/* node */
++
++.tree-theme-windows span.tree-junction {
++  display: block;
++  float: left;
++  width: 18px;
++  background-image: url(tree.gif);
++  background-position: -36px center;
++  background-repeat: no-repeat;
++}
++
++.tree-theme-windows a.tree-junction-expanded {
++  display: block;
++  float: left;
++  width: 18px;
++  background-image: url(tree.gif);
++  background-position: -18px center;
++  background-repeat: no-repeat;
++  text-decoration: none;
++}
++
++.tree-theme-windows a.tree-junction-collapsed {
++  display: block;
++  float: left;
++  width: 18px;
++  background-image: url(tree.gif);
++  background-position: 0px center;
++  background-repeat: no-repeat;
++  text-decoration: none;
++}
++
++.tree-theme-windows span.tree-content {
++  display: block;
++  margin-left: 18px;
++}
++
++/* tabletree */
++
++.tree-theme-windows table {
++  border-spacing: 0px;
++  border-collapse: collapse;
++}
++
++.tree-theme-windows tr.selected {
++  background-color: #0000ee;
++  color: #FFFFFF;
++}
++
++.tree-theme-windows td {
++  /* allow tree divs to fill complete td (regretfully height 100% on divs does not work) */
++  margin: 0px;
++  padding: 0px;
++}
++
++/* content */
++
++.tree-theme-windows span.tree-content a {
++  color: black;
++  text-decoration: none;
++}
++
++.tree-theme-windows span.tree-content a:hover {
++  text-decoration: underline;
++}
++
++.tree-theme-windows span.tree-content .selected span.tree-label {
++  background-color: #0000ee;
++  color: #FFFFFF;
++}
++
++.tree-theme-windows .tree-folder-closed {
++  padding-left: 18px;
++  background-image: url(folder-closed.gif);
++  background-position: 0px 50%;
++  background-repeat: no-repeat;
++}
++
++.tree-theme-windows .tree-folder-open {
++  padding-left: 18px;
++  background-image: url(folder-open.gif);
++  background-position: 0px 50%;
++  background-repeat: no-repeat;
++}
++
++.tree-theme-windows .tree-folder-other {
++  padding-left: 18px;
++  background-image: url(folder-other.gif);
++  background-position: 0px 50%;
++  background-repeat: no-repeat;
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/tree.gif
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/tree.gif
index 0000000,0000000..68fe235
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/theme/windows/tree.gif
@@@ -1,0 -1,0 +1,17 @@@
++GIF89aH
++
++�"�G�"�K*#�O:9#�SJY#�WZy��6v�喡�f�cYf�g6�f�kV�f�o���s�Y��w����{�٧���ڠ�y�嗆ƙ蟋
++Z(��*���R:)��bz��9Zꩦ�r*&���Jf��v�꣛�:��t�j��xҪ��|�ꧮ��z*������[���"�����2�+��:,���
++l�׶Jm��n+-��jkm�ݎ����*�ծ������.�������{����������';��/{��	7���
?������f����r���������o�r�+�r�/�s�3�\s�,���0�,��4m��8�lt�G��t�K�t�HG���LS��PO�u�[_�u�\���`�mv�hS���7�M��OÍ��_�M��g㝶���6���6��6�7�+>7�;^7��K~7�[�7�k���z�����^z�'�z�G�z�g{糓^�귳����޻�����?������������<��/o���c=��s���}k/���o:�蛏z�ȇ���߷���ӯ��������/��ē��
++P�
++ԧ�A��P����5jN��S��ԩ?�jP�:T���)�jR�:S�"5�_�)WÚձj�g�W�Zֵ���n��M��չ�ծr=*]�ZW��կy]�_ئְ�}�a�ب.ֱ���c%٪Nֲ��jf��Y�^V�x
-`EKX�"ִ�E-dUKY�bֵ��lK;���6��]mn[����6��
�m��[��ָ��E�o���:���=nt�;��V���ͮt�K� 
++ِ���8���o����I�h�)����ֆbX�☑&�	��Ƒ*�n,y�$��0��z8�!��(�-�n8��h�+Y�9ّ>��@��BY�y�<��4وLo=�����6�7�Y	�C��R��AɕJ��VYoX�Uٕeyog��bI�����֖M��[��c��r�ot��v��q�����:���֗W	�n��p����o�i��Y��y��I�D٘�8��i��i�������镉���y��Y�������2ٚ����������l9�~Y��i�����ٛ����y��y����Y���F���������ɚș�й�ҹ��I��i�ع�dٝsɛ�Y�������ٞ������䩗�ɗ�9��Y��9��Y��y����޹��ٟ�9������٠�ɞ�ɠ
++Z�z����$ʢ�
++��9p�y��9�J�j�!��
++�ʣ�
++�&*�(J�)Z�+ʡ-Z�/z��Yj�7��1*�3J�5ʥC�9Zp;��=*�?*�AJ�Oj�E��G��IʦK�M
++�[ʤW��_��S��U�]
++�gzpi:�k
++�mJ�cj�e��s��uʨw�a�y
++�o*�qJ�Q*�aʩ{�}��ʧ�j������������p�j��*��ꪑ
++��*�����j��������ʫ�ꫡʪ�j�����J��j�������~�

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/InverseSet.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/InverseSet.java
index 0000000,0000000..ad827ee
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/InverseSet.java
@@@ -1,0 -1,0 +1,143 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.util;
++
++import java.util.Collection;
++import java.util.Iterator;
++import java.util.Set;
++
++import org.apache.wicket.model.IDetachable;
++
++/**
++ * An inverse set.
++ * 
++ * @author svenmeier
++ */
++public class InverseSet<T> implements Set<T>, IDetachable
++{
++
++	private static final long serialVersionUID = 1L;
++
++	private Set<T> set;
++
++	/**
++	 * Create a full set.
++	 * 
++	 * @param set
++	 *            the contained set
++	 */
++	public InverseSet(Set<T> set)
++	{
++		this.set = set;
++	}
++
++	public void detach()
++	{
++		if (set instanceof IDetachable)
++		{
++			((IDetachable)set).detach();
++		}
++	}
++
++	public boolean isEmpty()
++	{
++		return !set.isEmpty();
++	}
++
++	public boolean contains(Object o)
++	{
++		return !set.contains(o);
++	}
++
++	public boolean add(T t)
++	{
++		return set.remove(t);
++	}
++
++	@SuppressWarnings("unchecked")
++	public boolean remove(Object o)
++	{
++		return set.add((T)o);
++	}
++
++	public boolean addAll(Collection<? extends T> ts)
++	{
++		boolean changed = false;
++
++		for (T t : ts)
++		{
++			changed |= set.remove(t);
++		}
++
++		return changed;
++	}
++
++	public boolean containsAll(Collection<?> cs)
++	{
++		for (Object c : cs)
++		{
++			if (set.contains(c))
++			{
++				return false;
++			}
++		}
++		return true;
++	}
++
++	@SuppressWarnings("unchecked")
++	public boolean removeAll(Collection<?> cs)
++	{
++		boolean changed = false;
++
++		for (Object c : cs)
++		{
++			changed |= set.add((T)c);
++		}
++
++		return changed;
++	}
++
++	public int size()
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public void clear()
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public Iterator<T> iterator()
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public boolean retainAll(Collection<?> c)
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public Object[] toArray()
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public <S> S[] toArray(S[] a)
++	{
++		throw new UnsupportedOperationException();
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/KeyingSet.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/KeyingSet.java
index 0000000,0000000..7ec798c
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/KeyingSet.java
@@@ -1,0 -1,0 +1,164 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.util;
++
++import java.io.Serializable;
++import java.util.Collection;
++import java.util.HashSet;
++import java.util.Iterator;
++import java.util.Set;
++
++import org.apache.wicket.model.AbstractReadOnlyModel;
++import org.apache.wicket.model.IModel;
++
++/**
++ * A set holding objects by their keys.
++ * 
++ * Note: Apart from {@link #contains(Object)} no query methods are supported.
++ * 
++ * @see #key(Object)
++ * 
++ * @author svenmeier
++ */
++public abstract class KeyingSet<T> implements Set<T>, Serializable
++{
++
++	private Set<Object> keys = new HashSet<Object>();
++
++	/**
++	 * Get the key for the given object.
++	 * 
++	 * @param t
++	 *            object to get key for
++	 */
++	protected abstract Object key(T t);
++
++	public int size()
++	{
++		return keys.size();
++	}
++
++	public boolean isEmpty()
++	{
++		return keys.isEmpty();
++	}
++
++	@SuppressWarnings("unchecked")
++	public boolean contains(Object o)
++	{
++		Object key = key((T)o);
++		if (key == null)
++		{
++			return false;
++		}
++		else
++		{
++			return keys.contains(key);
++		}
++	}
++
++	public boolean add(T t)
++	{
++		return keys.add(key(t));
++	}
++
++	@SuppressWarnings("unchecked")
++	public boolean remove(Object t)
++	{
++		return keys.remove(key((T)t));
++	}
++
++	public boolean containsAll(Collection<?> cs)
++	{
++		for (Object c : cs)
++		{
++			if (!contains(c))
++			{
++				return false;
++			}
++		}
++		return true;
++	}
++
++	@SuppressWarnings("unchecked")
++	public boolean addAll(Collection<? extends T> cs)
++	{
++		boolean changed = false;
++
++		for (Object c : cs)
++		{
++			changed |= add((T)c);
++		}
++
++		return changed;
++	}
++
++	@SuppressWarnings("unchecked")
++	public boolean removeAll(Collection<?> cs)
++	{
++		boolean changed = false;
++
++		for (Object c : cs)
++		{
++			changed |= remove(c);
++		}
++
++		return changed;
++	}
++
++	public void clear()
++	{
++		keys.clear();
++	}
++
++	public Iterator<T> iterator()
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public Object[] toArray()
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public <S> S[] toArray(S[] ts)
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public boolean retainAll(Collection<?> c)
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	/**
++	 * Create a model holding this set.
++	 * 
++	 * @return model
++	 */
++	public IModel<Set<T>> createModel()
++	{
++		return new AbstractReadOnlyModel<Set<T>>()
++		{
++			@Override
++			public Set<T> getObject()
++			{
++				return KeyingSet.this;
++			}
++		};
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/ProviderSubset.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/ProviderSubset.java
index 0000000,0000000..43b411d
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/ProviderSubset.java
@@@ -1,0 -1,0 +1,242 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.util;
++
++import java.util.Collection;
++import java.util.HashSet;
++import java.util.Iterator;
++import java.util.Set;
++
++import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider;
++import org.apache.wicket.model.AbstractReadOnlyModel;
++import org.apache.wicket.model.IDetachable;
++import org.apache.wicket.model.IModel;
++
++/**
++ * A subset of a {@link ITreeProvider}'s tree offering automatic detachment.
++ * 
++ * Make sure that the containing model calls {@link IDetachable#detach()} on its model object.
++ * 
++ * @see ITreeProvider#model(Object)
++ * 
++ * @author svenmeier
++ */
++public class ProviderSubset<T> implements Set<T>, IDetachable
++{
++
++	private static final long serialVersionUID = 1L;
++
++	private ITreeProvider<T> provider;
++
++	private Set<IModel<T>> models = new HashSet<IModel<T>>();
++
++	/**
++	 * Create an empty subset.
++	 * 
++	 * @param provider
++	 *            the provider of the complete set
++	 */
++	public ProviderSubset(ITreeProvider<T> provider)
++	{
++		this(provider, false);
++	}
++
++	/**
++	 * Create a subset optionally containing all roots of the provider.
++	 * 
++	 * @param provider
++	 *            the provider of the complete set
++	 * @param addRoots
++	 *            should all roots be added to this subset
++	 */
++	public ProviderSubset(ITreeProvider<T> provider, boolean addRoots)
++	{
++		this.provider = provider;
++
++		if (addRoots)
++		{
++			Iterator<? extends T> roots = provider.getRoots();
++			while (roots.hasNext())
++			{
++				add(roots.next());
++			}
++		}
++	}
++
++	public void detach()
++	{
++		for (IModel<T> model : models)
++		{
++			model.detach();
++		}
++	}
++
++	public int size()
++	{
++		return models.size();
++	}
++
++	public boolean isEmpty()
++	{
++		return models.size() == 0;
++	}
++
++	public void clear()
++	{
++		detach();
++
++		models.clear();
++	}
++
++	public boolean contains(Object o)
++	{
++		IModel<T> model = model(o);
++
++		boolean contains = models.contains(model);
++
++		model.detach();
++
++		return contains;
++	}
++
++	public boolean add(T t)
++	{
++		return models.add(model(t));
++	}
++
++	public boolean remove(Object o)
++	{
++		IModel<T> model = model(o);
++
++		boolean removed = models.remove(model);
++
++		model.detach();
++
++		return removed;
++	}
++
++	public Iterator<T> iterator()
++	{
++		return new Iterator<T>()
++		{
++
++			private Iterator<IModel<T>> iterator = models.iterator();
++
++			private IModel<T> current;
++
++			public boolean hasNext()
++			{
++				return iterator.hasNext();
++			}
++
++			public T next()
++			{
++				current = iterator.next();
++
++				return current.getObject();
++			}
++
++			public void remove()
++			{
++				iterator.remove();
++
++				current.detach();
++				current = null;
++			}
++		};
++	}
++
++	public boolean addAll(Collection<? extends T> ts)
++	{
++		boolean changed = false;
++
++		for (T t : ts)
++		{
++			changed |= add(t);
++		}
++
++		return changed;
++	}
++
++	public boolean containsAll(Collection<?> cs)
++	{
++		for (Object c : cs)
++		{
++			if (!contains(c))
++			{
++				return false;
++			}
++		}
++		return true;
++	}
++
++	public boolean removeAll(Collection<?> cs)
++	{
++		boolean changed = false;
++
++		for (Object c : cs)
++		{
++			changed |= remove(c);
++		}
++
++		return changed;
++	}
++
++	public boolean retainAll(Collection<?> c)
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public Object[] toArray()
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	public <S> S[] toArray(S[] a)
++	{
++		throw new UnsupportedOperationException();
++	}
++
++	@SuppressWarnings("unchecked")
++	private IModel<T> model(Object o)
++	{
++		return provider.model((T)o);
++	}
++
++	/**
++	 * Create a model holding this set.
++	 * 
++	 * @return model
++	 */
++	public IModel<Set<T>> createModel()
++	{
++		return new AbstractReadOnlyModel<Set<T>>()
++		{
++			@Override
++			public Set<T> getObject()
++			{
++				return ProviderSubset.this;
++			}
++
++			@Override
++			public void detach()
++			{
++				ProviderSubset.this.detach();
++			}
++		};
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/SortableTreeProvider.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/SortableTreeProvider.java
index 0000000,0000000..92d9e91
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/SortableTreeProvider.java
@@@ -1,0 -1,0 +1,97 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.util;
++
++import org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortState;
++import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider;
++import org.apache.wicket.extensions.markup.html.repeater.tree.ISortableTreeProvider;
++import org.apache.wicket.extensions.markup.html.repeater.util.SingleSortState;
++import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
++
++/**
++ * Convenience implementation of a tree provider that can also act as a locator for a
++ * {@link SingleSortState} object.
++ * 
++ * @author svenmeier
++ */
++public abstract class SortableTreeProvider<T> implements ISortableTreeProvider<T>
++{
++	private SingleSortState state = new SingleSortState();
++
++	/**
++	 * @see ISortableDataProvider#getSortState()
++	 */
++	public final ISortState getSortState()
++	{
++		return state;
++	}
++
++	/**
++	 * @see ISortableDataProvider#setSortState(org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortState)
++	 */
++	public final void setSortState(ISortState state)
++	{
++		if (!(state instanceof SingleSortState))
++		{
++			throw new IllegalArgumentException(
++				"argument [state] must be an instance of SingleSortState, but it is [" +
++					state.getClass().getName() + "]:[" + state.toString() + "]");
++		}
++		this.state = (SingleSortState)state;
++	}
++
++	/**
++	 * Returns current sort state
++	 * 
++	 * @return current sort state
++	 */
++	public SortParam getSort()
++	{
++		return state.getSort();
++	}
++
++	/**
++	 * Sets the current sort state
++	 * 
++	 * @param param
++	 *            parameter containing new sorting information
++	 */
++	public void setSort(SortParam param)
++	{
++		state.setSort(param);
++	}
++
++	/**
++	 * Sets the current sort state
++	 * 
++	 * @param property
++	 *            sort property
++	 * @param ascending
++	 *            sort direction
++	 */
++	public void setSort(String property, boolean ascending)
++	{
++		setSort(new SortParam(property, ascending));
++	}
++
++	/**
++	 * @see ISortableDataProvider#detach()
++	 */
++	public void detach()
++	{
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/TreeModelProvider.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/TreeModelProvider.java
index 0000000,0000000..d84c1dd
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/main/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/TreeModelProvider.java
@@@ -1,0 -1,0 +1,258 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.util;
++
++import java.io.Serializable;
++import java.util.ArrayList;
++import java.util.Iterator;
++import java.util.List;
++
++import javax.swing.event.TreeModelEvent;
++import javax.swing.event.TreeModelListener;
++import javax.swing.tree.TreeModel;
++
++import org.apache.wicket.ajax.AjaxRequestTarget;
++import org.apache.wicket.extensions.markup.html.repeater.tree.AbstractTree;
++import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider;
++import org.apache.wicket.model.IModel;
++
++/**
++ * A provider wrapping a Swing {@link TreeModel}.
++ * 
++ * EXPERIMENTAL !
++ * 
++ * @author svenmeier
++ * @param <T>
++ *            model object type
++ */
++public abstract class TreeModelProvider<T> implements ITreeProvider<T>
++{
++
++	private static final long serialVersionUID = 1L;
++
++	private final TreeModel treeModel;
++
++	private final boolean rootVisible;
++
++	protected boolean completeUpdate;
++
++	protected List<T> nodeUpdates;
++
++	protected List<T> branchUpdates;
++
++	/**
++	 * Wrap the given {@link TreeModel}.
++	 * 
++	 * @param treeModel
++	 *            model to wrap
++	 */
++	public TreeModelProvider(TreeModel treeModel)
++	{
++		this(treeModel, true);
++	}
++
++	/**
++	 * Wrap the given {@link TreeModel}.
++	 * 
++	 * @param treeModel
++	 *            the wrapped model
++	 * @param rootVisible
++	 *            should the root be visible
++	 */
++	public TreeModelProvider(TreeModel treeModel, boolean rootVisible)
++	{
++		this.treeModel = treeModel;
++		this.rootVisible = rootVisible;
++
++		treeModel.addTreeModelListener(new Listener());
++	}
++
++	public Iterator<T> getRoots()
++	{
++		if (rootVisible)
++		{
++			return new Iterator<T>()
++			{
++				boolean next = true;
++
++				public boolean hasNext()
++				{
++					return next;
++				}
++
++				public T next()
++				{
++					next = false;
++					return cast(treeModel.getRoot());
++				}
++
++				public void remove()
++				{
++					throw new UnsupportedOperationException();
++				}
++			};
++		}
++		else
++		{
++			return getChildren(cast(treeModel.getRoot()));
++		}
++	}
++
++	public boolean hasChildren(T object)
++	{
++		return !treeModel.isLeaf(object);
++	}
++
++	public Iterator<T> getChildren(final T object)
++	{
++		return new Iterator<T>()
++		{
++			private int size = treeModel.getChildCount(object);
++			private int index = -1;
++
++			public boolean hasNext()
++			{
++				return index < size - 1;
++			}
++
++			public T next()
++			{
++				index++;
++				return cast(treeModel.getChild(object, index));
++			}
++
++			public void remove()
++			{
++				throw new UnsupportedOperationException();
++			}
++		};
++	}
++
++	@SuppressWarnings("unchecked")
++	protected T cast(Object object)
++	{
++		return (T)object;
++	}
++
++	public abstract IModel<T> model(T object);
++
++	public void detach()
++	{
++		completeUpdate = false;
++		nodeUpdates = null;
++		branchUpdates = null;
++	}
++
++	/**
++	 * Call this method after all change to the wrapped {@link TreeModel} being initiated via
++	 * {@link AjaxRequestTarget}.
++	 * 
++	 * @param tree
++	 *            the tree utilizing this {@link ITreeProvider}
++	 * @param target
++	 *            the {@link AjaxRequestTarget} which initiated the changes
++	 */
++	public void update(AbstractTree<T> tree, AjaxRequestTarget target)
++	{
++		if (completeUpdate)
++		{
++			target.add(tree);
++		}
++		else
++		{
++			if (nodeUpdates != null)
++			{
++				for (T object : nodeUpdates)
++				{
++					tree.updateNode(object, target);
++				}
++			}
++
++			if (branchUpdates != null)
++			{
++				for (T object : branchUpdates)
++				{
++					tree.updateBranch(object, target);
++				}
++			}
++		}
++
++		detach();
++	}
++
++	protected void nodeUpdate(Object[] nodes)
++	{
++		if (nodeUpdates == null)
++		{
++			nodeUpdates = new ArrayList<T>();
++		}
++
++		for (Object node : nodes)
++		{
++			nodeUpdates.add(cast(node));
++		}
++	}
++
++	protected void branchUpdate(Object branch)
++	{
++		if (branchUpdates == null)
++		{
++			branchUpdates = new ArrayList<T>();
++		}
++
++		branchUpdates.add(cast(branch));
++	}
++
++	private class Listener implements TreeModelListener, Serializable
++	{
++		private static final long serialVersionUID = 1L;
++
++		public void treeNodesChanged(TreeModelEvent e)
++		{
++			if (e.getChildIndices() == null)
++			{
++				completeUpdate = true;
++			}
++			else
++			{
++				nodeUpdate(e.getChildren());
++			}
++		}
++
++		public void treeNodesInserted(TreeModelEvent e)
++		{
++			branchUpdate(e.getTreePath().getLastPathComponent());
++		}
++
++		public void treeNodesRemoved(TreeModelEvent e)
++		{
++			branchUpdate(e.getTreePath().getLastPathComponent());
++		}
++
++		public void treeStructureChanged(TreeModelEvent e)
++		{
++			if (e.getTreePath().getPathCount() == 1)
++			{
++				completeUpdate = true;
++			}
++			else
++			{
++				branchUpdate(e.getTreePath().getLastPathComponent());
++			}
++		}
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/NodeModelTest.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/NodeModelTest.java
index 0000000,0000000..9b127e4
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/NodeModelTest.java
@@@ -1,0 -1,0 +1,87 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.table;
++
++import junit.framework.Assert;
++
++import org.apache.wicket.model.IModel;
++import org.junit.Test;
++
++/**
++ * Test for {@link NodeModel}.
++ * 
++ * @author svenmeier
++ */
++public class NodeModelTest extends Assert
++{
++	/**
++	 * Test equality.
++	 */
++	@Test
++	public void equality()
++	{
++		NodeModel<String> model = new NodeModel<String>(new StringModel("A"), new boolean[] { true,
++				false });
++
++		assertTrue(model.equals(new NodeModel<String>(new StringModel("A"), new boolean[] { true,
++				false })));
++
++		assertFalse(model.equals(new NodeModel<String>(new StringModel("A"), new boolean[] { true,
++				true })));
++
++		assertFalse(model.equals(new NodeModel<String>(new StringModel("B"), new boolean[] { true,
++				false })));
++	}
++
++	private class StringModel implements IModel<String>
++	{
++
++		private static final long serialVersionUID = 1L;
++
++		private String string;
++
++		public StringModel(String string)
++		{
++			this.string = string;
++		}
++
++		public String getObject()
++		{
++			throw new UnsupportedOperationException();
++		}
++
++		public void setObject(String object)
++		{
++			throw new UnsupportedOperationException();
++		}
++
++		public void detach()
++		{
++		}
++
++		@Override
++		public boolean equals(Object obj)
++		{
++			if (obj instanceof StringModel)
++			{
++				return string.equals(((StringModel)obj).string);
++			}
++
++			return false;
++		}
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/TreeDataProviderTest.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/TreeDataProviderTest.java
index 0000000,0000000..5e794fb
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/table/TreeDataProviderTest.java
@@@ -1,0 -1,0 +1,128 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.table;
++
++import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.Iterator;
++import java.util.List;
++
++import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider;
++import org.apache.wicket.model.IModel;
++import org.apache.wicket.model.Model;
++import org.junit.Assert;
++import org.junit.Test;
++
++/**
++ * Test for {@link TreeDataProvider}.
++ */
++public class TreeDataProviderTest extends Assert
++{
++
++	/**
++	 * Test iterator.
++	 */
++	@Test
++	public void iterator()
++	{
++		TreeDataProvider<String> provider = new TreeDataProvider<String>(new TestProvider())
++		{
++			private static final long serialVersionUID = 1L;
++
++			@Override
++			protected boolean iterateChildren(String object)
++			{
++				return !object.startsWith("B");
++			}
++		};
++
++		assertEquals(3 + 2 * (3 + 3 * 3), provider.size());
++
++		Iterator<? extends String> iterator = provider.iterator(0, Integer.MAX_VALUE);
++		assertEquals("A", iterator.next());
++		assertEquals("AA", iterator.next());
++		assertEquals("AAA", iterator.next());
++		assertEquals("AAB", iterator.next());
++		assertEquals("AAC", iterator.next());
++		assertEquals("AB", iterator.next());
++		assertEquals("ABA", iterator.next());
++		assertEquals("ABB", iterator.next());
++		assertEquals("ABC", iterator.next());
++		assertEquals("AC", iterator.next());
++		assertEquals("ACA", iterator.next());
++		assertEquals("ACB", iterator.next());
++		assertEquals("ACC", iterator.next());
++		assertEquals("B", iterator.next());
++		// note: B is not iterated (i.e. collapsed)!
++		assertEquals("C", iterator.next());
++		assertEquals("CA", iterator.next());
++		assertEquals("CAA", iterator.next());
++		assertEquals("CAB", iterator.next());
++		assertEquals("CAC", iterator.next());
++		assertEquals("CB", iterator.next());
++		assertEquals("CBA", iterator.next());
++		assertEquals("CBB", iterator.next());
++		assertEquals("CBC", iterator.next());
++		assertEquals("CC", iterator.next());
++		assertEquals("CCA", iterator.next());
++		assertEquals("CCB", iterator.next());
++		assertEquals("CCC", iterator.next());
++
++		assertFalse(iterator.hasNext());
++	}
++
++	private static class TestProvider implements ITreeProvider<String>
++	{
++
++		private static final long serialVersionUID = 1L;
++
++		public Iterator<? extends String> getRoots()
++		{
++			return Arrays.asList("A", "B", "C").iterator();
++		}
++
++		public Iterator<? extends String> getChildren(String object)
++		{
++			List<String> children = new ArrayList<String>();
++
++			if (hasChildren(object))
++			{
++				for (int i = 0; i < 3; i++)
++				{
++					children.add(object + (char)('A' + i));
++				}
++			}
++
++			return children.iterator();
++		}
++
++
++		public boolean hasChildren(String object)
++		{
++			return object.length() < 3;
++		}
++
++		public IModel<String> model(String object)
++		{
++			return Model.of(object);
++		}
++
++		public void detach()
++		{
++		}
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/InverseSetTest.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/InverseSetTest.java
index 0000000,0000000..bcf4279
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/InverseSetTest.java
@@@ -1,0 -1,0 +1,77 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.util;
++
++import java.util.HashSet;
++
++import junit.framework.Assert;
++
++import org.apache.wicket.model.IDetachable;
++import org.junit.Test;
++
++/**
++ * Test for {@link InverseSet}.
++ * 
++ * @author svenmeier
++ */
++public class InverseSetTest extends Assert
++{
++	private TestSet set;
++
++	/**
++	 * Construct.
++	 */
++	public InverseSetTest()
++	{
++		set = new TestSet();
++		set.add("A");
++	}
++
++	/**
++	 * Test contains.
++	 */
++	@Test
++	public void contains()
++	{
++		InverseSet<String> inverse = new InverseSet<String>(set);
++		assertFalse(inverse.contains("A"));
++		assertTrue(inverse.contains("B"));
++
++		inverse.remove("B");
++		assertFalse(inverse.contains("A"));
++		assertFalse(inverse.contains("B"));
++
++		inverse.add("A");
++		assertTrue(inverse.contains("A"));
++		assertFalse(inverse.contains("B"));
++
++		inverse.detach();
++		assertTrue(set.detached);
++	}
++
++	private class TestSet extends HashSet<String> implements IDetachable
++	{
++		private static final long serialVersionUID = 1L;
++
++		boolean detached = false;
++
++		public void detach()
++		{
++			detached = true;
++		}
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/ProviderSubsetTest.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/ProviderSubsetTest.java
index 0000000,0000000..21ce444
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/ProviderSubsetTest.java
@@@ -1,0 -1,0 +1,170 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.util;
++
++import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.Iterator;
++import java.util.List;
++
++import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider;
++import org.apache.wicket.model.IModel;
++import org.junit.Assert;
++import org.junit.Test;
++
++/**
++ * Test for {@link ProviderSubset}.
++ * 
++ * @author svenmeier
++ */
++public class ProviderSubsetTest extends Assert
++{
++	private ITreeProvider<String> provider = new EmptyProvider();
++
++	/**
++	 * All models requested from the provider.
++	 */
++	private List<StringModel> models = new ArrayList<StringModel>();
++
++	/**
++	 * Test set methods.
++	 */
++	@Test
++	public void setMethods()
++	{
++		ProviderSubset<String> subset = new ProviderSubset<String>(provider);
++
++		subset.add("A");
++		subset.addAll(Arrays.asList("AA", "AAA"));
++
++		assertEquals(3, subset.size());
++
++		Iterator<String> iterator = subset.iterator();
++		assertTrue(iterator.hasNext());
++		iterator.next();
++		assertTrue(iterator.hasNext());
++		iterator.next();
++		assertTrue(iterator.hasNext());
++		iterator.next();
++		assertFalse(iterator.hasNext());
++		try
++		{
++			iterator.next();
++			fail();
++		}
++		catch (Exception expected)
++		{
++		}
++
++		assertTrue(subset.contains("A"));
++		assertTrue(subset.contains("AA"));
++		assertTrue(subset.contains("AAA"));
++
++		subset.createModel().detach();
++
++		for (StringModel model : models)
++		{
++			assertTrue(model.isDetached());
++		}
++
++		assertTrue(subset.contains("A"));
++		assertTrue(subset.contains("AA"));
++		assertTrue(subset.contains("AAA"));
++	}
++
++	private class StringModel implements IModel<String>
++	{
++
++		private static final long serialVersionUID = 1L;
++
++		private String string;
++
++		private boolean detached;
++
++		public StringModel(String string)
++		{
++			this.string = string;
++			models.add(this);
++		}
++
++		public String getObject()
++		{
++			detached = false;
++			return string;
++		}
++
++		public void setObject(String string)
++		{
++			detached = false;
++			this.string = string;
++		}
++
++		public void detach()
++		{
++			detached = true;
++		}
++
++		public boolean isDetached()
++		{
++			return detached;
++		}
++
++		@Override
++		public boolean equals(Object obj)
++		{
++			return string == ((StringModel)obj).string;
++		}
++
++		@Override
++		public int hashCode()
++		{
++			return string.hashCode();
++		}
++	}
++
++	private class EmptyProvider implements ITreeProvider<String>
++	{
++
++		private static final long serialVersionUID = 1L;
++
++		private List<String> EMPTY = new ArrayList<String>();
++
++		public Iterator<String> getRoots()
++		{
++			return EMPTY.iterator();
++		}
++
++		public boolean hasChildren(String object)
++		{
++			return false;
++		}
++
++		public Iterator<String> getChildren(String string)
++		{
++			throw new UnsupportedOperationException();
++		}
++
++		public IModel<String> model(String string)
++		{
++			return new StringModel(string);
++		}
++
++		public void detach()
++		{
++		}
++	}
++}

http://git-wip-us.apache.org/repos/asf/wicket/blob/05840d1c/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/TreeModelProviderTest.java
----------------------------------------------------------------------
diff --cc wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/TreeModelProviderTest.java
index 0000000,0000000..6851112
new file mode 100644
--- /dev/null
+++ b/wicket-extensions/src/test/java/org/apache/wicket/extensions/markup/html/repeater/tree/util/TreeModelProviderTest.java
@@@ -1,0 -1,0 +1,155 @@@
++/*
++ * 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.extensions.markup.html.repeater.tree.util;
++
++import java.util.Iterator;
++
++import javax.swing.tree.DefaultMutableTreeNode;
++import javax.swing.tree.DefaultTreeModel;
++import javax.swing.tree.MutableTreeNode;
++
++import org.apache.wicket.model.IModel;
++import org.apache.wicket.model.Model;
++import org.junit.Assert;
++import org.junit.Test;
++
++/**
++ * Test for {@link TreeModelProvider}.
++ * 
++ * @author svenmeier
++ */
++public class TreeModelProviderTest extends Assert
++{
++	private DefaultMutableTreeNode root;
++
++	private DefaultTreeModel treeModel;
++
++	/**
++	 * Construct.
++	 */
++	public TreeModelProviderTest()
++	{
++		root = new DefaultMutableTreeNode("JTree");
++		DefaultMutableTreeNode parent;
++
++		parent = new DefaultMutableTreeNode("colors");
++		root.add(parent);
++		parent.add(new DefaultMutableTreeNode("blue"));
++		parent.add(new DefaultMutableTreeNode("violet"));
++		parent.add(new DefaultMutableTreeNode("red"));
++		parent.add(new DefaultMutableTreeNode("yellow"));
++
++		parent = new DefaultMutableTreeNode("sports");
++		root.add(parent);
++		parent.add(new DefaultMutableTreeNode("basketball"));
++		parent.add(new DefaultMutableTreeNode("soccer"));
++		parent.add(new DefaultMutableTreeNode("football"));
++		parent.add(new DefaultMutableTreeNode("hockey"));
++
++		parent = new DefaultMutableTreeNode("food");
++		root.add(parent);
++		parent.add(new DefaultMutableTreeNode("hot dogs"));
++		parent.add(new DefaultMutableTreeNode("pizza"));
++		parent.add(new DefaultMutableTreeNode("ravioli"));
++		parent.add(new DefaultMutableTreeNode("bananas"));
++
++		treeModel = new DefaultTreeModel(root);
++	}
++
++	/**
++	 * Test roots and children.
++	 */
++	@Test
++	public void rootsAndChildren()
++	{
++		TreeModelProvider<DefaultMutableTreeNode> provider = new TreeModelProvider<DefaultMutableTreeNode>(
++			treeModel)
++		{
++			private static final long serialVersionUID = 1L;
++
++			@Override
++			public IModel<DefaultMutableTreeNode> model(DefaultMutableTreeNode object)
++			{
++				return Model.of(object);
++			}
++		};
++
++		Iterator<DefaultMutableTreeNode> roots = provider.getRoots();
++		assertTrue(roots.hasNext());
++		DefaultMutableTreeNode root = roots.next();
++		assertEquals("JTree", root.getUserObject());
++		assertFalse(roots.hasNext());
++
++		Iterator<DefaultMutableTreeNode> children = provider.getChildren(root);
++		assertTrue(children.hasNext());
++		assertEquals("colors", children.next().getUserObject());
++		assertTrue(children.hasNext());
++		assertEquals("sports", children.next().getUserObject());
++		assertTrue(children.hasNext());
++		assertEquals("food", children.next().getUserObject());
++		assertFalse(roots.hasNext());
++
++		treeModel.nodeChanged(root);
++	}
++
++	/**
++	 * Test updating.
++	 */
++	@Test
++	public void update()
++	{
++		TreeModelProvider<DefaultMutableTreeNode> provider = new TreeModelProvider<DefaultMutableTreeNode>(
++			treeModel)
++		{
++			private static final long serialVersionUID = 1L;
++
++			@Override
++			public IModel<DefaultMutableTreeNode> model(DefaultMutableTreeNode object)
++			{
++				return Model.of(object);
++			}
++		};
++
++		assertFalse(provider.completeUpdate);
++		assertEquals(null, provider.nodeUpdates);
++		assertEquals(null, provider.branchUpdates);
++
++		treeModel.removeNodeFromParent((MutableTreeNode)root.getChildAt(0).getChildAt(0));
++
++		assertFalse(provider.completeUpdate);
++		assertEquals(null, provider.nodeUpdates);
++		assertEquals(1, provider.branchUpdates.size());
++
++		treeModel.nodeChanged(root.getChildAt(1));
++
++		assertFalse(provider.completeUpdate);
++		assertEquals(1, provider.nodeUpdates.size());
++		assertEquals(1, provider.branchUpdates.size());
++
++		treeModel.nodeStructureChanged(root.getChildAt(2));
++
++		assertFalse(provider.completeUpdate);
++		assertEquals(1, provider.nodeUpdates.size());
++		assertEquals(2, provider.branchUpdates.size());
++
++		treeModel.setRoot(new DefaultMutableTreeNode("bam!"));
++
++		assertTrue(provider.completeUpdate);
++		assertEquals(1, provider.nodeUpdates.size());
++		assertEquals(2, provider.branchUpdates.size());
++	}
++}