/*******************************************************************************
* Copyright 2012 Geoscience Australia
*
* Licensed 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 au.gov.ga.earthsci.core.tree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import au.gov.ga.earthsci.common.util.AbstractTreePropertyChangeBean;
/**
* Abstract implementation of the {@link ITreeNode} interface.
*
* @author Michael de Hoog (michael.dehoog@ga.gov.au)
*
* @param <E>
* Type wrapped by this node.
*/
public abstract class AbstractTreeNode<E extends ITreeNode<E>> extends AbstractTreePropertyChangeBean implements
ITreeNode<E>
{
protected final E me;
protected E parent;
protected List<E> children = Collections.unmodifiableList(new ArrayList<E>());
protected AbstractTreeNode(Class<E> genericClass)
{
if (!genericClass.isInstance(this))
{
throw new IllegalStateException("Subclasses of " + AbstractTreeNode.class //$NON-NLS-1$
+ " must be an instance of the class specified by the generic argument"); //$NON-NLS-1$
}
me = genericClass.cast(this);
}
@Override
public E me()
{
return me;
}
@Override
public boolean isRoot()
{
return getParent() == null;
}
@Override
public E getParent()
{
return parent;
}
@Override
public void setParent(E parent, int indexInParent)
{
if (parent != null && parent.getChild(indexInParent) != this)
{
throw new IllegalArgumentException("Node is not a child of the given parent"); //$NON-NLS-1$
}
firePropertyChange("parent", getParent(), this.parent = parent); //$NON-NLS-1$
}
@Override
public boolean hasChildren()
{
return getChildCount() > 0;
}
@Override
public List<E> getChildren()
{
return children;
}
@Override
public int getChildCount()
{
return children.size();
}
/**
* Set this node's children.
*
* @param children
* Node's children, cannot be null
*/
public void setChildren(List<E> children)
{
List<E> oldValue = getChildren();
this.children = Collections.unmodifiableList(children);
//set children's parent to this
int i = 0;
for (E child : children)
{
child.setParent(me(), i++);
}
fireChildrenPropertyChange(oldValue, children);
}
/**
* Fire the "children" property change.
*
* @param oldChildren
* @param newChildren
*/
protected void fireChildrenPropertyChange(List<E> oldChildren, List<E> newChildren)
{
firePropertyChange("children", oldChildren, newChildren); //$NON-NLS-1$
}
@Override
public E getChild(int index)
{
return children.get(index);
}
@Override
public int index()
{
if (isRoot())
{
return -1;
}
return getParent().getChildren().indexOf(this);
}
@Override
public int depth()
{
return recurseDepth(getParent(), 0);
}
protected int recurseDepth(ITreeNode<?> node, int depth)
{
if (node == null)
{
return depth;
}
return recurseDepth(node.getParent(), depth + 1);
}
@Override
public void addChild(E child)
{
addChild(-1, child);
}
@Override
public void addChild(int index, E child)
{
// Handle the edge case of a child node being added that already exists
// Note - the rest of the tree API expects child arrays to act as sets
if (child.getParent() == this)
{
moveChild(child, index);
return;
}
if (child.getParent() != null)
{
child.getParent().removeChild(child);
}
if (index < 0 || index > children.size())
{
index = children.size();
}
List<E> newChildren = new ArrayList<E>(children);
newChildren.add(index, child);
setChildren(newChildren);
}
@Override
public void moveChild(E child, int newIndex)
{
if (newIndex < 0 || newIndex >= children.size())
{
newIndex = children.size() - 1;
}
int oldIndex = child.index();
if (oldIndex == newIndex)
{
return;
}
List<E> newChildren = new ArrayList<E>(children);
newChildren.remove(oldIndex);
newChildren.add(newIndex, child);
setChildren(newChildren);
}
@Override
public boolean removeChild(E child)
{
int index = child.index();
if (index < 0)
{
return false;
}
if (getChild(index) != child)
{
return false;
}
removeChild(index);
return true;
}
@Override
public E removeChild(int index)
{
if (index < 0 || index >= children.size())
{
throw new IndexOutOfBoundsException();
}
List<E> newChildren = new ArrayList<E>(children);
E node = newChildren.remove(index);
if (node.getParent() == this)
{
node.setParent(null, -1);
}
setChildren(newChildren);
return node;
}
@Override
public void clearChildren()
{
if (children.isEmpty())
{
return;
}
for (E child : children)
{
if (child.getParent() == this)
{
child.setParent(null, -1);
}
}
setChildren(new ArrayList<E>());
}
@Override
public void removeFromParent()
{
if (isRoot())
{
return;
}
@SuppressWarnings("unchecked")
E e = (E) this;
getParent().removeChild(e);
}
@Override
public boolean replaceChild(E child, E newChild)
{
int index = child.index();
if (index < 0)
{
return false;
}
if (getChild(index) != child)
{
return false;
}
List<E> newChildren = new ArrayList<E>(children);
newChildren.remove(index);
if (child.getParent() == this)
{
child.setParent(null, -1);
}
newChildren.add(index, newChild);
setChildren(newChildren);
return true;
}
@Override
public List<E> pathToRoot()
{
List<E> path = new ArrayList<E>();
E node = me();
while (node != null)
{
path.add(0, node);
node = node.getParent();
}
return path;
}
@Override
public int[] indicesToRoot()
{
int count = depth();
int[] indices = new int[count];
ITreeNode<E> node = this;
for (int i = count - 1; i >= 0; i--)
{
indices[i] = node.index();
node = node.getParent();
}
return indices;
}
@Override
public boolean hasParentInPathToRoot(ITreeNode<?> parent)
{
ITreeNode<E> node = this;
while (node != null)
{
if (node == parent)
{
return true;
}
node = node.getParent();
}
return false;
}
@Override
public E getRoot()
{
E node = me();
while (!node.isRoot())
{
node = node.getParent();
}
return node;
}
}