
com.sangupta.jerry.ds.Tree Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jerry-core Show documentation
Show all versions of jerry-core Show documentation
Common Java functionality for core functionality
The newest version!
/**
*
* jerry - Common Java Functionality
* Copyright (c) 2012-2017, Sandeep Gupta
*
* http://sangupta.com/projects/jerry-core
*
* 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 com.sangupta.jerry.ds;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.sangupta.jerry.transform.Transformer;
import com.sangupta.jerry.util.AssertUtils;
/**
* A simple n-ary Tree data-structure useful to keep arbitrary data in a
* tree-shape form.
*
* This class is NOT thread-safe.
*
* @author sangupta
*
* @param
* the type of instance, that nodes of this {@link Tree} will hold
*/
public class Tree implements Iterable> {
/**
* The data that this node keeps
*/
private T data;
/**
* The parent node to this node
*
*/
private Tree parent;
/**
* List of all child nodes
*
*/
private List> children;
/**
* Convenience constructor
*
* @param data
* the data to hold in this node of {@link Tree}
*/
public Tree(T data) {
this.data = data;
}
// Common methods
/**
* Check if this node is the root of the tree.
*
* @return true
if this node is root, false
* otherwise
*/
public boolean isRoot() {
return this.parent == null;
}
/**
* Check if this node is a leaf node, one without any children.
*
* @return true
if this node is leaf, false
* otherwise
*/
public boolean isLeaf() {
if(this.children == null) {
return true;
}
return this.children.isEmpty();
}
/**
* Check if this node has child elements or not.
*
* @return true
if this node has atleast one child,
* false
otherwise
*/
public boolean hasChildren() {
if(this.children == null) {
return false;
}
return !this.children.isEmpty();
}
/**
* Return the level of this node in the tree.
*
* @return the level for this node, where ROOT node is assigned a level of
* zero
*/
public int getLevel() {
if(this.isRoot()) {
return 0;
}
return this.parent.getLevel() + 1;
}
/**
* Add a new node as a child node to this node and return the newly added
* node back.
*
* @param data
* the data for the added child node
*
* @return the added child node
*/
public Tree addChild(T data) {
if(this.children == null) {
this.children = new ArrayList>();
}
Tree child = new Tree(data);
child.parent = this;
this.children.add(child);
return child;
}
/**
* Return the children associated with this node.
*
* @return a list of all child {@link Tree} nodes
*/
public List> getChildren() {
return this.children;
}
/**
* Remove this very node from the tree.
*
* @return true
if we could successfully remove the node from
* its parent, false
otherwise
*/
boolean removeSelf() {
if(this.isRoot()) {
return true;
}
boolean removed = this.parent.removeChild(this);
if(!removed) {
return false;
}
this.parent = null;
return true;
}
/**
* Remove the given child from the list of children for this node.
*
* @param node
* the node to be removed from this node's children
*
* @return true
if node was removed, false
* otherwise
*/
boolean removeChild(Tree node) {
if(node == null) {
return false;
}
if(AssertUtils.isEmpty(this.children)) {
return false;
}
Iterator> childIterator = this.children.iterator();
while(childIterator.hasNext()) {
Tree child = childIterator.next();
if(node == child) {
childIterator.remove();
return true;
}
}
return false;
}
/**
* Get the child node at the given index
*
* @param index
* the index for which the children is desired
*
* @return the child node at given index
*
* @throws IllegalArgumentException
* if index is less than zero
*
* @throws IndexOutOfBoundsException
* if index is not valid, or there are no children present
*/
Tree getChild(int index) {
if(index < 0) {
throw new IllegalArgumentException("Index cannot be negative");
}
if(this.children == null) {
throw new IndexOutOfBoundsException("No child present");
}
int size = this.children.size();
if(index >= size) {
throw new IndexOutOfBoundsException("Index cannot be greater than size of children");
}
return this.children.get(index);
}
/**
* Get the next sibling for this node.
*
* @return the next sibling for this node, null
otherwise
*/
public Tree getSibling() {
if(this.parent == null) {
return null;
}
return this.parent.nextChild(this);
}
/**
* Get the next child node from this node where the node being searched is
* provided.
*
* @param node
* the node whose next sibling is desired
*
* @return the next node {@link Tree} instance, null
otherwise
*/
public Tree nextChild(Tree node) {
if(this.isLeaf()) {
return null;
}
for(int index = 0; index < this.children.size(); index++) {
// the following comparison is == intentionally because we are looking for exact node
// reference and not two nodes that have same data
if(this.children.get(index) == node) {
// found return next
if(index >= this.children.size() - 1) {
return null;
}
return this.children.get(index + 1);
}
}
return null;
}
@Override
public Iterator> iterator() {
return new SimpleTreeIterator(this);
}
@Override
public String toString() {
if(this.data != null) {
return this.data.toString();
}
return "SimpleTreeNode(null)";
}
// Usual accessors follow
public Tree getParent() {
return this.parent;
}
/**
* Read the data stored in this node.
*
* @return the data in this node
*/
public T getData() {
return this.data;
}
/**
* Change the data stored in this node.
*
* @param data the data to be set
*/
public void setData(T data) {
this.data = data;
}
/**
* Render the tree into a {@link String}.
*
* @param transformer
* the {@link Transformer} instance to call on each node
*
* @return the {@link String} representation of the tree as generated using
* the given {@link Transformer} instance
*/
public String renderTree(Transformer transformer) {
List lines = renderTreeLines(this, transformer);
String newline = System.getProperty("line.separator");
StringBuilder sb = new StringBuilder(lines.size() * 20);
for (StringBuilder line : lines) {
sb.append(line);
sb.append(newline);
}
return sb.toString();
}
private List renderTreeLines(Tree tree, Transformer transformer) {
List result = new LinkedList();
String treeData = transformer.transform(tree.getData());
result.add(new StringBuilder().append(treeData));
if(tree.hasChildren()) {
Iterator> iterator = tree.getChildren().iterator();
while (iterator.hasNext()) {
List subtree = renderTreeLines(iterator.next(), transformer);
if (iterator.hasNext()) {
addSubtree(result, subtree);
} else {
addLastSubtree(result, subtree);
}
}
}
return result;
}
private void addSubtree(List result, List subtree) {
Iterator iterator = subtree.iterator();
result.add(iterator.next().insert(0, "├── "));
while (iterator.hasNext()) {
result.add(iterator.next().insert(0, "│ "));
}
}
private void addLastSubtree(List result, List subtree) {
Iterator iterator = subtree.iterator();
result.add(iterator.next().insert(0, "└── "));
while (iterator.hasNext()) {
result.add(iterator.next().insert(0, " "));
}
}
// STATIC CLASS FOR ITERATOR follows
/**
* This is a depth-first iterator for the tree.
*
* @author sangupta
*
* @param
* the type of instance, the nodes of this {@link Iterator} holds
*/
public static class SimpleTreeIterator implements Iterator> {
/**
* The node that will be returned next
*/
private Tree nextNode = null;
/**
* Whether the root node has been consumed by the iterator or not
*
*/
private boolean rootConsumed = false;
/**
* Indicates if we have the next node available or not
*/
protected volatile boolean hasNextNode;
/**
* The constructor to generate the iterator
*
* @param rootNode
* the node that will act as the root
*/
public SimpleTreeIterator(Tree rootNode) {
if(rootNode == null) {
throw new IllegalArgumentException("Rootnode to iterate on cannot be null");
}
this.nextNode = rootNode;
}
@Override
public boolean hasNext() {
return this.hasNextNode;
}
/**
* Internal method that fills in the next node that will be returned to
* the user.
*
* @return true
if there is a next node present,
* false
otherwise
*/
private boolean hasNextInternal() {
// send the root first
if(!rootConsumed) {
this.rootConsumed = true;
return true;
}
// find the next node
if(this.nextNode.hasChildren()) {
this.nextNode = this.nextNode.getChild(0);
return true;
}
do {
Tree sibling = this.nextNode.getSibling();
if(sibling != null) {
this.nextNode = sibling;
return true;
}
if(this.nextNode.isRoot()) {
return false;
}
// if sibling is null, go back to its parent
this.nextNode = this.nextNode.getParent();
} while(true);
}
@Override
public Tree next() {
this.hasNextNode = hasNextInternal();
return this.nextNode;
}
@Override
public void remove() {
if(this.nextNode == null) {
return;
}
this.nextNode.removeSelf();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy