package org.jabref.model;
import java.util.Objects;
import java.util.Optional;
/**
* Represents a node in a chain.
* We view a chain as a vertical hierarchy and thus refer to the previous node as parent and the next node is a child.
* <p>
* In usual implementations, nodes function as wrappers around a data object. Thus normally they have a value property
* which allows access to the value stored in the node.
* In contrast to this approach, the ChainNode<T> class is designed to be used as a base class which provides the
* tree traversing functionality via inheritance.
* <p>
* Example usage:
* private class BasicChainNode extends ChainNode<BasicChainNode> {
* public BasicChainNode() {
* super(BasicChainNode.class);
* }
* }
*
* @param <T> the type of the class
*/
@SuppressWarnings("unchecked") // We use some explicit casts of the form "(T) this". The constructor ensures that this cast is valid.
public abstract class ChainNode<T extends ChainNode<T>> {
/**
* This node's parent, or null if this node has no parent
*/
private T parent;
/**
* This node's child, or null if this node has no child
*/
private T child;
/**
* Constructs a chain node without parent and no child.
*
* @param derivingClass class deriving from TreeNode<T>. It should always be "T.class".
* We need this parameter since it is hard to get this information by other means.
*/
public ChainNode(Class<T> derivingClass) {
parent = null;
child = null;
if (!derivingClass.isInstance(this)) {
throw new UnsupportedOperationException("The class extending ChainNode<T> has to derive from T");
}
}
/**
* Returns this node's parent or an empty Optional if this node has no parent.
*
* @return this node's parent T, or an empty Optional if this node has no parent
*/
public Optional<T> getParent() {
return Optional.ofNullable(parent);
}
/**
* Sets the parent node of this node.
* <p>
* This method does not set this node as the child of the new parent nor does it remove this node
* from the old parent. You should probably call {@link #moveTo(ChainNode)} to change the chain.
*
* @param parent the new parent
*/
protected void setParent(T parent) {
this.parent = Objects.requireNonNull(parent);
}
/**
* Returns this node's child or an empty Optional if this node has no child.
*
* @return this node's child T, or an empty Optional if this node has no child
*/
public Optional<T> getChild() {
return Optional.ofNullable(child);
}
/**
* Adds the node as the child. Also sets the parent of the given node to this node.
* The given node is not allowed to already be in a tree (i.e. it has to have no parent).
*
* @param child the node to add as child
* @return the child node
* @throws UnsupportedOperationException if the given node has already a parent
*/
public T setChild(T child) {
Objects.requireNonNull(child);
if (child.getParent().isPresent()) {
throw new UnsupportedOperationException("Cannot add a node which already has a parent, use moveTo instead");
}
child.setParent((T) this);
this.child = child;
return child;
}
/**
* Removes this node from its parent and makes it a child of the specified node.
* In this way the whole subchain based at this node is moved to the given node.
*
* @param target the new parent
* @throws NullPointerException if target is null
* @throws UnsupportedOperationException if target is an descendant of this node
*/
public void moveTo(T target) {
Objects.requireNonNull(target);
// Check that the target node is not an ancestor of this node, because this would create loops in the tree
if (this.isAncestorOf(target)) {
throw new UnsupportedOperationException("the target cannot be a descendant of this node");
}
// Remove from previous parent
getParent().ifPresent(oldParent -> oldParent.removeChild());
// Add as child
target.setChild((T) this);
}
/**
* Removes the child from this node's child list, giving it an empty parent.
*
*/
public void removeChild() {
if (child != null) {
// NPE if this is ever called
child.setParent(null);
}
child = null;
}
/**
* Returns true if this node is an ancestor of the given node.
* <p>
* A node is considered an ancestor of itself.
*
* @param anotherNode node to test
* @return true if anotherNode is a descendant of this node
* @throws NullPointerException if anotherNode is null
*/
public boolean isAncestorOf(T anotherNode) {
Objects.requireNonNull(anotherNode);
if (anotherNode == this) {
return true;
} else {
return child.isAncestorOf(anotherNode);
}
}
/**
* Adds the given node at the end of the chain.
* E.g., "A > B > C" + "D" -> "A > B > C > D".
*/
public void addAtEnd(T node) {
if (child == null) {
setChild(node);
} else {
child.addAtEnd(node);
}
}
}