/*
* $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.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.eclipse.xtext.xbase.lib.Pure;
import org.arakhne.afc.math.tree.TreeNodeAddedEvent;
import org.arakhne.afc.math.tree.TreeNodeListener;
import org.arakhne.afc.math.tree.TreeNodeParentChangedEvent;
import org.arakhne.afc.math.tree.TreeNodeRemovedEvent;
/**
* This is the generic implementation of a tree
* tree with a weak reference to its parent node.
*
* @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
*/
public abstract class AbstractTreeNode<D, N extends AbstractTreeNode<D, N>> extends AbstractParentlessTreeNode<D, N> {
private static final long serialVersionUID = -296917015483866693L;
/** Parent of this node.
*/
private transient WeakReference<N> parent;
/** Construct node.
* @param useLinkedList indicates if a linked list must be used to store the data.
* If <code>false</code>, an ArrayList will be used.
*/
public AbstractTreeNode(boolean useLinkedList) {
super(useLinkedList);
this.parent = null;
}
/** Construct node.
* @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 AbstractTreeNode(boolean useLinkedList, boolean copyDataCollection, List<D> data) {
super(useLinkedList, copyDataCollection, data);
this.parent = null;
}
/** Construct node.
* @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 AbstractTreeNode(boolean useLinkedList, Collection<D> data) {
super(useLinkedList, data);
this.parent = null;
}
/** Construct node.
* @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 AbstractTreeNode(boolean useLinkedList, D data) {
super(useLinkedList, data);
this.parent = null;
}
@Override
@Pure
public int getDepth() {
final N p = getParentNode();
if (p == null) {
return 0;
}
return p.getDepth() + 1;
}
@Override
public abstract boolean setChildAt(int index, N newChild) throws IndexOutOfBoundsException;
@Override
@Pure
public final N getParentNode() {
return this.parent == null ? null : this.parent.get();
}
/** Set the reference to the parent node.
*
* @param newParent is the new parent.
* @param fireEvent indicates if the event must be fired or not.
* @return <code>true</code> if an event must be fired due to
* the action of this function, otherwise <code>false</code>.
* @since 4.0
*/
boolean setParentNodeReference(N newParent, boolean fireEvent) {
final N oldParent = getParentNode();
if (newParent == oldParent) {
return false;
}
this.parent = (newParent == null) ? null : new WeakReference<>(newParent);
if (!fireEvent) {
return true;
}
firePropertyParentChanged(oldParent, newParent);
if (oldParent != null) {
oldParent.firePropertyParentChanged(toN(), oldParent, newParent);
}
return false;
}
@Override
public N removeFromParent() {
final N lparent = getParentNode();
if (lparent != null) {
final int index = lparent.indexOf(toN());
if (index != -1) {
lparent.setChildAt(index, null);
}
}
return lparent;
}
@Override
public void removeDeeplyFromParent() {
N lparent = getParentNode();
N child = toN();
while (lparent != null && lparent.isEmpty()) {
child = lparent;
lparent = lparent.getParentNode();
}
if (lparent != null) {
final int index = lparent.indexOf(child);
if (index != -1) {
lparent.setChildAt(index, null);
}
}
}
/** Fire the event for the node child sets.
*
* @param childIndex is the index of the child that was set. If the added node was root, this parameter is <code>0</code>.
* @param newChild is the child that was added.
*/
protected final void firePropertyChildAdded(int childIndex, N newChild) {
firePropertyChildAdded(new TreeNodeAddedEvent(toN(), childIndex, newChild));
}
/** Fire the event for the node child sets.
*
* @param event the event.
*/
void firePropertyChildAdded(TreeNodeAddedEvent event) {
if (this.nodeListeners != null) {
for (final TreeNodeListener listener : this.nodeListeners) {
if (listener != null) {
listener.treeNodeChildAdded(event);
}
}
}
final N parentNode = getParentNode();
assert parentNode != this;
if (parentNode != null) {
parentNode.firePropertyChildAdded(event);
}
}
/** Fire the event for the removed node child.
*
* @param childIndex is the index of the child that was removed. If the added node was root, this parameter is <code>0</code>.
* @param oldChild is the child that was removed.
*/
protected final void firePropertyChildRemoved(int childIndex, N oldChild) {
firePropertyChildRemoved(new TreeNodeRemovedEvent(toN(), childIndex, oldChild));
}
/** Fire the event for the removed node child.
*
* @param event the event.
*/
void firePropertyChildRemoved(TreeNodeRemovedEvent event) {
if (this.nodeListeners != null) {
for (final TreeNodeListener listener : this.nodeListeners) {
if (listener != null) {
listener.treeNodeChildRemoved(event);
}
}
}
final N parentNode = getParentNode();
if (parentNode != null) {
parentNode.firePropertyChildRemoved(event);
}
}
/** Fire the event for the changes node parents.
*
* @param oldParent is the previous parent node
* @param newParent is the current parent node
*/
protected final void firePropertyParentChanged(N oldParent, N newParent) {
firePropertyParentChanged(new TreeNodeParentChangedEvent(toN(), oldParent, newParent));
}
/** Fire the event for the changes node parents.
*
* @param node is the node for which the parent has changed.
* @param oldParent is the previous parent node
* @param newParent is the current parent node
*/
void firePropertyParentChanged(N node, N oldParent, N newParent) {
firePropertyParentChanged(new TreeNodeParentChangedEvent(node, oldParent, newParent));
}
/** Fire the event for the changes node parents.
*
* @param event the event.
*/
void firePropertyParentChanged(TreeNodeParentChangedEvent event) {
if (this.nodeListeners != null) {
for (final TreeNodeListener listener : this.nodeListeners) {
if (listener != null) {
listener.treeNodeParentChanged(event);
}
}
}
final N parentNode = getParentNode();
if (parentNode != null) {
parentNode.firePropertyParentChanged(event);
}
}
@Override
@Pure
public final boolean isRoot() {
return this.parent == null;
}
@Override
@SuppressWarnings("unchecked")
@Pure
public final N[] getChildren(Class<N> type) {
final N[] array = (N[]) Array.newInstance(type, getChildCount());
getChildren(array);
return array;
}
@Override
@Pure
public final Iterator<N> children() {
return new ChildIterator();
}
/** Move this node in the given new node.
*
* <p>If any child node is already present
* at the given position in the new parent node,
* the tree node may replace the existing node
* or insert the moving node according to its
* implementation.
*
* <p>This function is preferred to a sequence of calls
* to {@link #removeFromParent()} and {@link #setChildAt(int, AbstractTreeNode)}
* because it fires a limited set of events dedicated to the move
* the node.
*
* @param newParent is the new parent for this node.
* @param index is the position of this node in the new parent.
* @param isDynamicChildList indicates if the parent node has a dynamic list of children.
* If <code>true</code> the given {@code index} is clamped to
* avoid {@link IndexOutOfBoundsException} when inserting this node in the parent node.
* If <code>false</code> an {@link IndexOutOfBoundsException} is thrown when
* the given {@code index} is outside the range of children of the parent node.
* @return <code>true</code> on success, otherwise <code>false</code>.
*/
@SuppressWarnings("checkstyle:npathcomplexity")
protected boolean moveTo(N newParent, int index, boolean isDynamicChildList) {
if (newParent == null) {
return false;
}
int newIndex = index;
if (isDynamicChildList) {
if (newIndex < 0) {
newIndex = 0;
} else if (newIndex > getChildCount()) {
newIndex = getChildCount();
}
} else if (newIndex < 0 || newIndex >= getChildCount()) {
return false;
}
final N oldParent = getParentNode();
if (oldParent == newParent) {
return false;
}
final N me = toN();
// Remove from previous parent
int oldIndex = -1;
if (oldParent != null) {
oldIndex = oldParent.indexOf(me);
if (oldIndex >= 0) {
oldParent.setChildAtWithoutEventFiring(oldIndex, null);
}
}
// Remove the node at the target position
if (!isDynamicChildList || newIndex < getChildCount()) {
newParent.setChildAt(newIndex, null);
}
// Add inside the new parent
newParent.setChildAtWithoutEventFiring(newIndex, me);
// Set the parent reference
final boolean fireEvent = setParentNodeReference(newParent, false);
// Fire events
if (oldIndex >= 0) {
assert oldParent != null;
oldParent.firePropertyChildRemoved(oldIndex, me);
}
if (newIndex >= 0) {
newParent.firePropertyChildAdded(newIndex, me);
}
if (fireEvent) {
firePropertyParentChanged(oldParent, newParent);
}
return true;
}
/** Invoked by the inner classes to set the child at the given index
* without firing the events. This function should never
* invoke {@link #setParentNodeReference(AbstractTreeNode, boolean)}.
*
* @param index is the position of the new child.
* @param child is the new child
* @throws IndexOutOfBoundsException if the index is invalid.
*/
protected abstract void setChildAtWithoutEventFiring(int index, N child) throws IndexOutOfBoundsException;
/**
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 13.0
*/
private class ChildIterator implements Iterator<N> {
private int idx;
private N next;
/** Construct iterator.
*/
ChildIterator() {
searchNext();
}
private void searchNext() {
final int count = AbstractTreeNode.this.getChildCount();
this.next = null;
while (this.next == null && this.idx < count) {
this.next = AbstractTreeNode.this.getChildAt(this.idx);
++this.idx;
}
}
@Override
public boolean hasNext() {
return this.next != null;
}
@Override
public N next() {
if (this.next == null) {
throw new NoSuchElementException();
}
final N n = this.next;
searchNext();
return n;
}
}
}