/*
* $Id$
* This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc
*
* Copyright (c) 2000-2012 Stephane GALLAND.
* Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports,
* Universite de Technologie de Belfort-Montbeliard.
* Copyright (c) 2013-2016 The original authors, and other authors.
*
* 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 org.arakhne.afc.math.tree.node;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.math.tree.TreeNode;
/**
* This is the generic implementation of a n-ary
* tree. This node has not a constant count of children.
* Indeed when a child node was removed, the count of children
* is also changed. It also means that a child node is never
* <code>null</code>. If you want a generic implementation
* of a tree node which has always the same count of children,
* please see {@link ConstantNaryTreeNode}.
*
* <p><h3>moveTo</h3>
* According to its definition in
* {@link TreeNode#moveTo(TreeNode, int)}, the binary
* tree node implementation of <code>moveTo</code>
* does not replace any existing node at the position given as
* parameter of <code>moveTo</code>. In place <code>moveTo</code>
* insert the moving node in the new parent.
*
* @param <D> is the type of the data inside the tree
* @param <N> is the type of the tree nodes.
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
* @see ConstantNaryTreeNode
*/
public abstract class NaryTreeNode<D, N extends NaryTreeNode<D, N>> extends AbstractTreeNode<D, N> {
private static final long serialVersionUID = -1313340976961548532L;
private List<N> children;
/**
* Empty node.
*/
public NaryTreeNode() {
this(DEFAULT_LINK_LIST_USE);
}
/**
* @param data are the initial user data.
*/
public NaryTreeNode(Collection<D> data) {
super(DEFAULT_LINK_LIST_USE, data);
this.children = null;
}
/**
* @param data are the initial user data.
*/
public NaryTreeNode(D data) {
this(DEFAULT_LINK_LIST_USE, data);
}
/**
* @param useLinkedList indicates if a linked list must be used to store the data.
* If <code>false</code>, an ArrayList will be used.
*/
public NaryTreeNode(boolean useLinkedList) {
super(useLinkedList);
this.children = null;
}
/**
* @param useLinkedList indicates if a linked list must be used to store the data.
* If <code>false</code>, an ArrayList will be used.
* @param copyDataCollection indicates if the given data collection is copied
* if <code>true</code> or the inner data collection will be the given
* collection itself if <code>false</code>.
* @param data are the initial user data
*/
public NaryTreeNode(boolean useLinkedList, boolean copyDataCollection, List<D> data) {
super(useLinkedList, copyDataCollection, data);
this.children = null;
}
/**
* @param useLinkedList indicates if a linked list must be used to store the data.
* If <code>false</code>, an ArrayList will be used.
* @param data are the initial user data
*/
public NaryTreeNode(boolean useLinkedList, D data) {
super(useLinkedList, data);
this.children = null;
}
@Pure
@Override
public Class<? extends Enum<?>> getPartitionEnumeration() {
return null;
}
private List<N> newInternalList(int size) {
return new ArrayList<>(size);
}
/** Invoked when this object must be deserialized.
*
* @param in is the input stream.
* @throws IOException in case of input stream access error.
* @throws ClassNotFoundException if some class was not found.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
if (this.children != null) {
final N me = toN();
for (final NaryTreeNode<D, N> child : this.children) {
if (child != null) {
child.setParentNodeReference(me, false);
}
}
}
}
/** Clear the tree.
*
* <p>Caution: this method also destroyes the
* links between the child nodes inside the tree.
* If you want to unlink the first-level
* child node with
* this node but leave the rest of the tree
* unchanged, please call <code>setChildAt(i,null)</code>.
*/
@Override
public void clear() {
removeAllUserData();
if (this.children != null) {
final List<N> nodes = new ArrayList<>(this.children);
while (!this.children.isEmpty()) {
setChildAt(0, null);
}
for (final N child : nodes) {
child.clear();
}
nodes.clear();
}
}
@Pure
@Override
public final int getChildCount() {
return (this.children == null) ? 0 : this.children.size();
}
@Pure
@Override
public int getNotNullChildCount() {
return this.notNullChildCount;
}
@Pure
@Override
public final int indexOf(N child) {
int i = 0;
if (this.children != null) {
for (final N cchild : this.children) {
if (cchild == child) {
return i;
}
++i;
}
}
return -1;
}
@Pure
@Override
public final N getChildAt(int index) throws IndexOutOfBoundsException {
if (this.children != null) {
return this.children.get(index);
}
throw new IndexOutOfBoundsException();
}
/** Move this node in the given new parent node.
*
* <p>This function adds this node at the end of the list
* of the children of the new parent node.
*
* <p>This function is preferred to a sequence of calls
* to {@link #removeFromParent()} and {@link #setChildAt(int, NaryTreeNode)}
* because it fires a limited set of events dedicated to the move
* the node.
*
* @param newParent is the new parent for this node.
* @return <code>true</code> on success, otherwise <code>false</code>.
*/
public boolean moveTo(N newParent) {
if (newParent == null) {
return false;
}
return moveTo(newParent, newParent.getChildCount());
}
@Override
public boolean moveTo(N newParent, int index) {
return moveTo(newParent, index, true);
}
@Override
@SuppressWarnings("checkstyle:npathcomplexity")
public boolean setChildAt(int index, N newChild) throws IndexOutOfBoundsException {
final int count = (this.children == null) ? 0 : this.children.size();
if (index < 0 || index >= count) {
throw new IndexOutOfBoundsException();
}
final N oldChild = this.children.get(index);
if (oldChild == newChild) {
return false;
}
if (oldChild != null) {
oldChild.setParentNodeReference(null, true);
--this.notNullChildCount;
firePropertyChildRemoved(index, oldChild);
}
if (newChild != null) {
final N oldParent = newChild.getParentNode();
if (oldParent != this) {
newChild.removeFromParent();
}
}
// set the element
if (newChild != null) {
this.children.set(index, newChild);
} else {
this.children.remove(index);
}
if (newChild != null) {
newChild.setParentNodeReference(toN(), true);
++this.notNullChildCount;
firePropertyChildAdded(index, newChild);
}
return true;
}
@Override
protected void setChildAtWithoutEventFiring(int index, N newChild) throws IndexOutOfBoundsException {
final int count = (this.children == null) ? 0 : this.children.size();
int insertionIndex = index;
if (insertionIndex < 0) {
insertionIndex = 0;
}
if (newChild != null) {
if (insertionIndex > count) {
insertionIndex = count;
}
if (this.children == null) {
this.children = newInternalList(count + 1);
}
this.children.add(insertionIndex, newChild);
++this.notNullChildCount;
} else {
if (insertionIndex >= count) {
insertionIndex = count - 1;
}
final N oldValue = this.children.remove(insertionIndex);
if (this.children.isEmpty()) {
this.children = null;
}
if (oldValue != null) {
--this.notNullChildCount;
}
}
}
@Override
public final boolean removeChild(N child) {
if (child != null && this.children != null) {
final int index = this.children.indexOf(child);
if (index >= 0 && index < this.children.size()) {
this.children.remove(index);
--this.notNullChildCount;
child.setParentNodeReference(null, true);
firePropertyChildRemoved(index, child);
return true;
}
}
return false;
}
/** Add a child node at the end of the children list.
*
* @param child is the new child to insert.
* @return <code>true</code> on success, otherwise <code>false</code>
*/
public final boolean addChild(N child) {
if (child != null) {
return addChild(getChildCount(), child);
}
return false;
}
/** Add a child node at the specified index.
*
* @param index is the insertion index.
* @param newChild is the new child to insert.
* @return <code>true</code> on success, otherwise <code>false</code>
*/
public final boolean addChild(int index, N newChild) {
if (newChild == null) {
return false;
}
final int count = (this.children == null) ? 0 : this.children.size();
final N oldParent = newChild.getParentNode();
if (oldParent != this && oldParent != null) {
newChild.removeFromParent();
}
final int insertionIndex;
if (this.children == null) {
this.children = newInternalList(index + 1);
}
if (index < count) {
// set the element
insertionIndex = Math.max(index, 0);
this.children.add(insertionIndex, newChild);
} else {
// Resize the array
insertionIndex = this.children.size();
this.children.add(newChild);
}
++this.notNullChildCount;
newChild.setParentNodeReference(toN(), true);
firePropertyChildAdded(insertionIndex, newChild);
return true;
}
@Pure
@Override
public final boolean isLeaf() {
return (this.children == null) || this.children.isEmpty();
}
@Override
public void getChildren(Object[] array) {
if (array != null && this.children != null) {
this.children.toArray(array);
}
}
@Pure
@Override
public int getMinHeight() {
int min = Integer.MAX_VALUE;
boolean set = false;
if (this.children != null) {
for (final N child : this.children) {
if (child != null) {
if (set) {
min = Math.min(min, child.getMinHeight());
} else {
min = child.getMinHeight();
set = true;
}
}
}
}
return 1 + (set ? min : 0);
}
@Pure
@Override
public int getMaxHeight() {
int max = Integer.MIN_VALUE;
boolean set = false;
if (this.children != null) {
for (final N child : this.children) {
if (child != null) {
if (set) {
max = Math.max(max, child.getMaxHeight());
} else {
max = child.getMaxHeight();
set = true;
}
}
}
}
return 1 + (set ? max : 0);
}
@Override
protected void getHeights(int currentHeight, List<Integer> heights) {
if (isLeaf()) {
heights.add(new Integer(currentHeight));
} else {
for (final N child : this.children) {
if (child != null) {
child.getHeights(currentHeight + 1, heights);
}
}
}
}
/**
* This is the generic implementation of a n-ary
* tree.
*
* @param <D> is the type of the data inside the tree
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
public static class DefaultNaryTreeNode<D> extends NaryTreeNode<D, DefaultNaryTreeNode<D>> {
private static final long serialVersionUID = -6775932201459338929L;
/**
* Empty node.
*/
public DefaultNaryTreeNode() {
super();
}
/** Construct a node.
* @param data are the initial user data
*/
public DefaultNaryTreeNode(Collection<D> data) {
super(data);
}
/** Construct a node.
* @param data are the initial user data
*/
public DefaultNaryTreeNode(D data) {
super(data);
}
}
}