Java - Generic interface for visitors - java

I use the visitor pattern to execute functions on a hierarchy of classes. For example:
// Node.java
public abstract class Node {}
// Addition.java
public final class Addition extends Node {
public final Node e1;
public final Node e2;
public Addition(final Node e1, final Node e2) {
this.e1 = e1;
this.e2 = e2;
}
}
// Multiplication.java
public final class Multiplication extends Node {
public final Node e1;
public final Node e2;
public Multiplication(final Node e1, final Node e2) {
this.e1 = e1;
this.e2 = e2;
}
}
// Constant.java
public final class Constant extends Node {
public final int value;
public Constant(final int value) {
this.value = value;
}
}
Since I have different functions taking a node and returning different types, I define an interface for my visitors:
public interface INodeVisitor<T> {
public abstract T visit(final Addition add);
public abstract T visit(final Multiplication add);
public abstract T visit(final Constant add);
public default T visit(final Node node) {
throw new RuntimeException("Unknown node: " + node.getClass() + ".");
}
}
Then I can define a generic method accept() for all my nodes which allows me to define new visitors with no need to modify the nodes:
// I add this in the definition of every node.
public <T> T accept(final INodeVisitor<T> visitor) {
return visitor.visit(this);
}
I can now define functions on nodes, for example:
// This visitor is a function on Nodes and returns an Integer.
public final class NodeEvaluationVisitor implements INodeVisitor<Integer> {
public final Integer visit(final Addition add) {
return add.e1.accept(this) + add.e2.accept(this);
}
public final Integer visit(final Multiplication mul) {
return mul.e1.accept(this) * mul.e2.accept(this);
}
public final Integer visit(final Constant c) {
return c.value;
}
}
This works perfectly:
public class Main {
public static Integer eval(final Node n) {
return n.accept(new NodeEvaluationVisitor());
}
public static void main(final String[] args) {
final Node n = new Addition(new Multiplication(new Constant(5), new Constant(8)), new Constant(2));
System.out.println(eval(n));
}
}
Output:
42
This works because my interface has access to all the hierarchy. My problem arises when Node is defined in projectA, and its subclasses are defined in projectB which depends on projectA.
Since Node also needs to define an accept() method that takes an INodeVisitor as argument, I define my interface in projectA. I can then define my concrete visitors in projectB.
However, this means that my interface needs to be generic about the subclasses of Node, which does not seem to be possible. With this interface:
public interface INodeVisitor<T> {
public default T visit(final Node node) {
throw new RuntimeException("Unknown node: " + node.getClass() + ".");
}
}
I get:
Exception in thread "main" java.lang.RuntimeException: Unknown node: class Addition.
at INodeVisitor.visit(INodeVisitor.java:5)
at Addition.accept(Addition.java:14)
at Main.eval(Main.java:9)
at Main.main(Main.java:14)
Since Java's dynamic dispatch chooses the default method. Same result with:
public interface INodeVisitor<T> {
public default <U extends Node> T visit(final U node) {
throw new RuntimeException("Unknown node: " + node.getClass() + ".");
}
}
The only way I found to make this work is to do the dispatch manually:
public interface INodeVisitor<T> {
public abstract T visit(final Node node);
}
And so in NodeEvaluationVisitor:
#Override
public final Integer visit(final Node node) {
if (node instanceof Addition)
return visit((Addition) node);
if (node instanceof Multiplication)
return visit((Multiplication) node);
if (node instanceof Constant)
return visit((Constant) node);
throw new RuntimeException("Unknown node: " + node.getClass() + ".");
}
Question: Is there a better solution in Java than the unsatisfactory manual dispatch solution above? Thank you for reading and for your suggestions.
Edit: The solution I implemented in the end (still unsatisfactory) is to add a specific interface, say INodeSpecificVisitor<T>, in projectB, have the visitors implement this specific interface, and do the manual dispatch in INodeSpecificVisitor. That way at least the manual dispatch is factorized between the visitors.

Related

Java: Turning a tree into a stream

One of the advantages of streams is that you can avoid visiting the whole structure for some operations, like anyMatch or filter+findFirst.
However, if you have your own data structure, depending on how you turn it into a stream you may end up visiting it all anyway.
What is the right way to turn a custom tree data type into a stream?
Consider the following example:
interface Tree{
void forEach(Consumer<Integer> c);
}
final class EmptyTree implements Tree{
public void forEach(Consumer<Integer> c){}
}
interface NonEmptyTree extends Tree{}
record Leave(int label) implements NonEmptyTree{
public void forEach(Consumer<Integer> c){
System.out.println("In forEachLeave "+label);
c.accept(label);
}
}
record Node(NonEmptyTree left, NonEmptyTree right) implements NonEmptyTree{
public void forEach(Consumer<Integer> c){
left.forEach(c); right.forEach(c);
}
}
The two main ways to turn a tree into a stream would be
var sb=Stream.<Integer>builder();
myTree.forEach(sb);
sb.build()
or
Stream.of(myTree).mapMulti(Tree::forEach)
However, both of them call forEach, thus both of them will visit all the tree (and call the prints for all the labels, in this example).
How do you implement a .stream() method in the Tree type so that it would not even visit the whole tree if it is not needed? (because of .anyMatch, for example)
Ok, I sorted it.
I'm quite sure that what I'm doing is pretty standard with immutable trees
(parent fields only make sense in mutable trees)
Here is my result, for reference for future programmers doing streams on immutable trees.
The class TreeIterator<E> is the one really relevant to this ordeal.
I could make nested classes to be able to make more stuff private, but as a code example I think it is more clear in this non nested form.
interface Tree<E> extends Iterable<E>{
Tree<E> and(Tree<E> other);
default Tree<E> left(){ return empty(); }
default Tree<E> right(){ return empty(); }
default E label(Supplier<E> orElse){ return orElse.get(); }
#SuppressWarnings("unchecked")
static <E> Tree<E> empty(){ return (Tree<E>)EmptyTree.empty; }
static <E> Tree<E> leaf(E label){ return new Leaf<E>(label); }
default Stream<E> stream(){ return StreamSupport.stream(spliterator(), false); }
}
final class EmptyTree<E> implements Tree<E>{
public Tree<E> and(Tree<E> other){ return other; }
private EmptyTree(){} //Singleton pattern: only one EmptyTree can exists
static final Tree<?> empty = new EmptyTree<>();
public Iterator<E> iterator(){ return List.<E>of().iterator(); }
public String toString(){ return "<EMPTY>"; }
}
interface NonEmptyTree<E> extends Tree<E>{
Leaf<E> itAdvance(ArrayList<NonEmptyTree<E>> stack);
default Tree<E> and(Tree<E> other){
if (!(other instanceof NonEmptyTree<E> net)){ return this; }
return new Node<E>(this, net);
}
}
record Leaf<E>(E label) implements NonEmptyTree<E>{
public E label(Supplier<E> orElse){ return label; }
public Leaf<E> itAdvance(ArrayList<NonEmptyTree<E>> stack){ return this; }
public Iterator<E> iterator(){ return List.<E>of(label).iterator(); }
public String toString(){ return label+""; }
}
record Node<E>(NonEmptyTree<E> left, NonEmptyTree<E> right) implements NonEmptyTree<E>{
public Node{ assert left!=null && right!=null; }//null is not a valid tree
public Leaf<E> itAdvance(ArrayList<NonEmptyTree<E>> stack){
stack.add(right);
return left.itAdvance(stack);
}
public Iterator<E> iterator(){ return new TreeIterator<E>(this); }
public String toString(){ return "("+left+", "+right+")"; }
}
class TreeIterator<E> implements Iterator<E>{
private final ArrayList<NonEmptyTree<E>> stack = new ArrayList<>(32);
public boolean hasNext(){ return !stack.isEmpty(); }
public TreeIterator(Node<E> n){ stack.add(n); }
public E next(){
if(stack.isEmpty()){ throw new NoSuchElementException(); }
var last=stack.remove(stack.size()-1);
return last.itAdvance(stack).label();
}
}
Looking at record definition Leave(int label) implements NonEmptyTree, I have two questions:
Did you mean "leaf"?
A tree consists of nodes (either a leaf or an internal node), but a node or leaf do not implement a tree, i.e., they are not are specific type of a tree. Are you sure about your node/leaf/tree implementation?
I would recommend a simple implementation like this one here: https://www.baeldung.com/java-binary-tree
When it comes to stream, you have two options:
Implement your own Stream-enabled class (see discussion here)
Provide a method that returns a (specific) stream, e.g. filtered.
Keep in mind that there are many different trees out there, e.g. red–black tree, n-ary tree, AVL Tree, B-Tree ...

Overriding a method where parameter type references own class

I have the following java code:
public class TreeNode<T> {
public TreeNode<T> getParent() {
return null;
}
public void setParent(TreeNode<T> parent) {
}
public List<TreeNode<T>> getChildren() {
return null;
}
public void setChildren(List<TreeNode<T>> children) {
}
public T getData() {
return null;
}
public void setData(T data) {
}
}
Now, I want to create a class that extends the one above, like the following:
public class BinaryTreeNode<T> extends TreeNode<T> {
public BinaryTreeNode<T> getLeftChild() {
return null;
}
public void setLeftChild() {
}
public BinaryTreeNode<T> getRightChild() {
return null;
}
public void setRightChild() {
}
#Override
public void setChildren(List<BinaryTreeNode<T>> children) {
}
}
However, the last method won't compile because the parameter children is not of type List<TreeNode<T>>. I understand why this happens (because List<BinaryTreeNode<T>> is not considered a subtype of List<TreeNode<T>>), but what is the best way to fix something like this?
I know that I can just define the children parameter to be of type List<TreeNode<T>>, but if possible I would like to enforce it to be of type List<BinaryTreeNode<T>>.
The issue is not coming from the lists not being subclasses, it's because you can't override a method and upcast its parameter.
if you have the following classes:
public class A
{
public void f(A a) {}
}
public class B extends A
{
public void problem(){}
public void f(B a)
{
a.problem();
}
}
Since B extends from A, you could have the following code running:
A a1 = new B();
A a2 = new A();
a1.f(a2);
when that code will run, it will try to execute the function "problem" on an A instance that doesn't have it.
That's why in general you can't override the parameters with classes that inherit the original type.
Now, you can leave the parameter as List> children, but then you might get nodes that aren't binary, which I assume you don't want.
If you're sure that's the way you want the inheritance to be, you can check the type of "children" at the beginning of the method and throw an exception if it doesn't fit.
To sum up what shmosel proposed:
public class TreeNode<T, N extends TreeNode<T, N>> {
public N getParent() { return null; }
public void setParent(N parent) {}
public List<N> getChildren() { return null; }
public void setChildren(List<N> children) {}
public T getData() { return null; }
public void setData(T data) {}
}
The downside is that your Nodes now have to by typed with a "redundant" generic type. That is because you try to counter the fact that sub classes can act as object of super classes.
Your Binary Node class would look like this:
class BinaryTreeNode<T> extends TreeNode<T, BinaryTreeNode<T>>
As an alternative and for further reading, try this article:
https://www.sitepoint.com/self-types-with-javas-generics/

Sorting objects in a recursive generic data structure

I have a generic tree class in which each tree node holds some data. Each piece of data has one attribute of the type String. I want to sort each tree node's children alphabetically by this attribute.
The Tree class:
public class Tree<T>{
public T data;
public List<Tree<T>> children = new ArrayList<Tree<T>>();
}
Note that the tree's children are of type Tree!
An example actual type parameter for the Tree class is the following:
public class DataItem{
public String name;
}
My idea is to extend the Tree class with a sort() method and use a Comparator like the following but I am stuck at the comparison function:
public class Tree<T>{
public T data;
public List<Tree<T>> children = new ArrayList<Tree<T>>();
public void sort(){
Collections.sort(this.children,
new Comparator<Tree<T>>(){
#Override
public int compare(Tree<T> objectA, Tree<T> objectB){
//I am stuck here!
return 0;
}
}
);
for(Tree<T> child: this.children){
child.sort();
}
}
}
I have different ideas to solve this problem:
Use reflection to acces the objects' attributes and compare them.
Implement the interface Comparable in DataItem.
Use a new interface to acces the objects' attribute for comparison:
public interface GetComparisonAttribute {
public String getComparisonAttribute();
}
public class DataItem implements GetComparisonAttribute{
public String name;
#Override
public String GetComparisonAttribute(){
return this.name;
}
}
//the comparison function inside Tree<T>.sort():
public int compare(Tree<T> objectA, Tree<T> objectB){
return objectA.data.getComparisonAttribute()
.compareToIgnoreCase(objectB.data.getComparisonAttribute());
}
What is the right or best thing to do? Are there any other ways?
It may be important to be able to specify the sorting attribute.
I think it would be nice to use Collections.sort() directly on a Tree but implementing it in this recursive data structure really confuses me. A downside of doing it this way is that I cannot specify the sorting attribute.
Try this:
public class Tree<T> {
public T data;
public List<Tree<T>> children = new ArrayList<Tree<T>>();
private Class<T> type;
public Tree(Class<T> t) {
type = t;
}
public void sort(){
Collections.sort(this.children,
new Comparator<Tree<T>>(){
#Override
public int compare(Tree<T> objectA, Tree<T> objectB){
if (type==DataItem.class)
{
DataItem diA = (DataItem) (objectA.data);
DataItem diB = (DataItem) (objectB.data);
return diA.name.compareTo(diB.name);
}
else
throw new IllegalArgumentException();
}
}
);
for(Tree<T> child: this.children){
child.sort();
}
}
}
You should pass the type T of the class Tree when you create it. Then you can downcast to DataItem and sort the list according to the filed you like. You can check of course also against other type parameters aside from DataItem.
public void sort(final Comparator<? super T> dataComparator)
{
Collections.sort(this.children,
new Comparator<Tree<T>>()
{
#Override
public int compare(Tree<T> treeA, Tree<T> treeB)
{
return dataComparator.compare(treeA.getData(), treeB.getData());
}
}
);
for(Tree<T> child: this.children)
{
child.sort(dataComparator);
}
}
void test()
{
Tree<DataItem> tree = new Tree<>();
tree.sort(new Comparator<DataItem>()
{
#Override
public int compare(DataItem dataA, DataItem dataB)
{
return dataA.getName().compareTo(dataB.getName());
}
});
}
In java8, this can be simplified as
public void sort(final Comparator<? super T> dataComparator)
{
Collections.sort(this.children,
Comparator.comparing(Tree::getData, dataComparator));
for(Tree<T> child: this.children)
{
child.sort(dataComparator);
}
}
void test()
{
Tree<DataItem> tree = new Tree<>();
tree.sort( Comparator.comparing(DataItem::getName) );
}

Generalise rendering of a tree of unknown nodes

What I am trying to do isn't easy; I do realize that. But I would think there is some better way that what I cooked up. Here's the Problem: I am trying to write a generic class to render a Tree that stores Nodes which are evaluable - for example, a Node could be storing a value and just evaluating to that value, or it could be generating a random value, or be an operation on a number of other Nodes - hence I say tree, but the truth is that the internal state of the Node is unknown (for example, a Node may have 0,1,2 or more Node fields for the Children, or an ArrayList of children etc). Of course this wouldn't be an issue if each Node knew how to render itself, but I am trying to avoid this. (Ideally, I would like to be able to render the Tree to a String or as an OpenGL graphic or whatever just by changing the Renderer). Oh and please don't ask questions like "What good would that be?" because I'm just doing this because it seemed interesting.
(By now I figured that I could at least give Nodes the knowledge that they CAN be rendered, and the ability to decide the logical structure of the rendering, but this probably wouldn't improve this too much)
This is what I have so far:
An interface for the Nodes
public interface Node<T> {
T evaluate();
}
A class for value nodes:
public class ValueNode<T> implements Node<T> {
private T value;
#Override
public T evaluate() {
return value;
}
public ValueNode(T value) {
this.value = value;
}
}
A general class for binary operators:
public abstract class BinaryOperator<A, B, T> implements Node<T> {
private Node<A> left;
private Node<B> right;
public BinaryOperator(Node<A> left, Node<B> right) {
this.left = left;
this.right = right;
}
public Node<A> getLeft() {
return left;
}
public Node<B> getRight() {
return right;
}
}
A class for Integer addition:
/**
* I couldn't figure out a generic way to add 2 numbers,
* so I'm going with just Integer for now.
*
*/
public class IntegerAdditionNode extends BinaryOperator<Integer, Integer, Integer> {
public IntegerAdditionNode (Node<Integer> left, Node<Integer> right) {
super(left,right);
}
public Integer evaluate() {
return getLeft().evaluate() + getRight().evaluate();
}
}
And finally, an example string renderer class that allows for new rendering options to be added dynamically. It's extremely ugly though, and I would really appreciate ideas or just a light push in the right direction how I could do this one better:
import java.util.HashMap;
public class NodeToString {
public interface RenderMethod {
public <T extends Node<?>> String renderNode(T node);
}
public static void main(String[] args) {
//test
NodeToString renderer = new NodeToString();
RenderMethod addRender = new RenderMethod() {
private NodeToString render;
public RenderMethod addNodeToString(NodeToString render) {
this.render = render;
return this;
}
#Override
public <T extends Node<?>> String renderNode(T node) {
IntegerAdditionNode addNode = (IntegerAdditionNode) node;
return render.render(addNode.getLeft()) +"+"+render.render(addNode.getRight());
}
}.addNodeToString(renderer);
renderer.addRenderMethod(IntegerAdditionNode.class, addRender);
RenderMethod valueRender = new RenderMethod () {
#Override
public <T extends Node<?>> String renderNode(T node) {
return ((ValueNode<?>)node).evaluate().toString();
}
};
//I don't know why I have to cast here. But it doesn't compile
//if I don't.
renderer.addRenderMethod((Class<? extends Node<?>>) ValueNode.class,
valueRender);
Node<Integer> node = new IntegerAdditionNode(new ValueNode<Integer>(2),
new ValueNode<Integer>(3));
System.out.println(renderer.render(node));
}
private HashMap<Class<? extends Node<?>>, RenderMethod> renderMethods = new
HashMap<Class<? extends Node<?>>, NodeToString.RenderMethod>();
/**
* Renders a Node
* #param node
* #return
*/
public <T extends Node<?>> String render(T node) {
Class nodeType = node.getClass();
if(renderMethods.containsKey(nodeType)) {
return renderMethods.get(nodeType).renderNode(node);
} else {
throw new RuntimeException("Unknown Node Type");
}
}
/**
* This adds a rendering Method for a specific Node type to the Renderer
* #param nodeType
* #param method
*/
public void addRenderMethod(Class<? extends Node<?>> nodeType, RenderMethod method) {
renderMethods.put(nodeType, method);
}
}
I know you say that you are "trying to avoid this" but I really think the Node should have a render() method on it. If you don't want the render code to be in each node type then you could do something like:
public class ValueNode<T> implements Node<T> {
private static RenderMethod rendorMethod;
public static void setRendorMethod(RenderMethod rendorMethod) {
ValueNode.rendorMethod = rendorMethod;
}
...
public String render() {
rendorMethod(this);
}
The way you are doing it will work although I found it unnecessarily complicated. Some comments:
renderer.addRenderMethod(IntegerAdditionNode.class, addRender)
Calling addRenderMethod with a value of addRender took me a while to understand for obvious reasons since add has multiple meanings here. Maybe using the verb put which matched the Map is better.
Calling NodeToString.render() from within your addRender() was confusing although I understand the need.
Method is an overloaded term in Java which makes RenderMethod seem like a Java method not a way of rendering. How about NodeRenderer or just Renderer?
Assigning NodeString to the variable renderer just is strange. It is very different from an addRender or valueRender since it is not a RenderMethod. Maybe it should be?
In all main classes, the first thing I do in the main method is to do something like the following. This makes it less complicated for me at least:
public static void main(String[] args) {
new NodeString().doMain(args);
}
private void doMain(String[] args) {
...

Write a tree class in Java where each level has a unique object type

I need to write a tree class in Java where each level has a unique object type. The way it is written below does not take advantage of generics and causes alot of duplicate code. Is there a way to write this with Generics ?
public class NodeB {
private String nodeValue;
//private List<NodeB> childNodes;
// constructors
// getters/setters
}
public class NodeA {
private String value;
private List<NodeB> childNodes;
// constructors
// getters/setters
}
public class Tree {
private String value;
private List<NodeA> childNodes;
// constructors
// tree methods
}
This is simplistic implementation, but enough to give general idea:
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class GenericNode {
public static abstract class AbstractNode<V, N> {
private V value;
private List<N> children;
public AbstractNode(V value, N... children) {
this.value = value;
this.children = children != null ? Arrays.asList(children)
: Collections.<N> emptyList();
}
public V getValue() {
return value;
}
public List<N> getChildren() {
return children;
}
public int getNumberOfChildren() {
return children.size();
}
#Override
public String toString() {
return value.toString() + "->" + children.toString();
}
}
// leaf node type, ignore type of children
public static class NodeB extends AbstractNode<String, Object> {
public NodeB(String value, Object... nodes) {
super(value, nodes);
}
}
// example of typical node in the mid of tree
public static class NodeA extends AbstractNode<String, NodeB> {
public NodeA(String value, NodeB... nodes) {
super(value, nodes);
}
}
// top level node type
public static class Tree extends AbstractNode<String, NodeA> {
public Tree(String value, NodeA... nodes) {
super(value, nodes);
}
}
#SuppressWarnings({ "rawtypes", "unchecked" })
public static <V, N extends AbstractNode> int getNodeCount(
AbstractNode<V, N> node) {
int nodeCount = node.getChildren().size();
for (N child : node.getChildren()) {
nodeCount += getNodeCount(child);
}
return nodeCount;
}
public static void main(String[] args) {
NodeB nodeB1 = new NodeB("Leaf node 1");
NodeB nodeB2 = new NodeB("Leaf node 2");
NodeA nodeA = new NodeA("Node with children", nodeB1, nodeB2);
NodeA emptyNodeA = new NodeA("Empty node");
Tree tree = new Tree("Tree", nodeA, emptyNodeA);
System.out.println(tree);
System.out.println(1 + getNodeCount(tree));
}
}
You could make N and V types implement specific interfaces so it will be possible to call some common operations on values and/or children.
EDIT: updated implementation with recursive method for node count retrieval
All you need is a Pair<A, B>. Example of trees:
Pair<A, Pair<B, C>>
Pair<Pair<A, B>, Pair<C, D>>
Pair<Pair<Pair<A, B>, Pair<C, D>>, Pair<Pair<E, F>, Pair<G, H>>
ps: don't do this. :)
This is an ideal spot for everything inheriting from "Node", but even that is unnecessary.\
What you probably want is a single generic "Node" object that contains references to your different classes (use composition before inheritance).
At that point, each of your different classes probably has something that can be done to them (otherwise why are they all in the same data structure?) Have them implement a common interface with this common functionality. The node class can delegate to this interface, or some other class can extract the class by this interface and act on it.
This would be better than trying to force something to also BE a node--do one simple thing and do it well.
--edit--
I can't really add an example that is relevant to you because you didn't post anything about your scenario.
But let's say that you have these different classes A, B * C. First of all are they related AT ALL aside from all being children of Object? Let's say they all implement interface "Iface". (If not, you can just replace Iface with "Object", but this really implies a bad design.)
Anyway, your "Node" object is just one object--
public class Node {
private List<node> children;
private Iface myObject;
... setters, getters, tree implementation, tree navigation, related garbage...
}
Now this is enough to create your tree. One thing that you might be able to do to make things smoother, have "Node implements Iface" and delegate any calls to it's object. For instance, if Iface contains an eat(Food foodtype) method, your node could implement Iface and have a method:
public void eat(Food foodtype) {
myObject.eat(foodtype);
}
This would make the "Node" class act as though it was the class it contained.
By the way--another relatively good idea at this point would be to make myObject "private final" and ensure it is not null in the constructor. That way you would always know it was set and none of your delegated members would have to do null checks.
I don't think generics are going to help you much in this case. Instead of having a different class for each level in the tree. What about one node class that has children and store a different class on each level. That should help eliminate a lot of the duplication.
I'm fairly new to Java, so this might have issues I'm not aware of, but it seems to work on a simple level at least.
Define your main Node class - this one will be the root of the tree.
public class NodeA {
private String _value;
private ArrayList<NodeA> _children;
private int _depth;
public NodeA (String value, int depth) {
_value = value;
_children = new ArrayList<NodeA>();
_depth = depth;
}
//probably want getters for _children and _value here
//this makes a new child, with its type depending on the depth of the tree it will
//be placed at. NodeB and NodeC will both inherit from NodeA
public void add(String value) {
switch (_depth) {
case 0:
_children.add(new NodeB(value, _depth+1));
break;
case 1:
_children.add(new NodeC(value, _depth+1));
break;
}
}
The add() method is going to create a new child for the node using the specified value. If you initialize the root of the tree as a NodeA with depth 0, you can add children to nodes and the tree should end up populated so that the next level contains all NodeB's, and the next all NodeC's. The code for NodeB and NodeC is super simple and could be replicated to create an arbitrary amount of Node levels (here is that code).
public class NodeB extends NodeA {
public NodeB(String value, int depth) {
super(value, depth);
}
//nothing else needed!
The code for NodeC is identical, except for the obvious replacements of B's with C's.
Hope this helps / is the kind of answer you wanted!

Categories