dnet-core/dnet-core-components/src/main/java/eu/dnetlib/miscutils/hstree/TreeNode.java

253 lines
7.6 KiB
Java

package eu.dnetlib.miscutils.hstree;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This class implement statically typed complete tree of finite depth. The type signature of each tree node determines
* fully the depth and the types of all the children.
*
* <pre>
* class MyTree extends TreeNode&lt;RootPayload, L1Payload, TreeNode&lt;L1Payload, L2Payload, TreeNode&lt;L2Payload, Void, NilTreeNode&gt;&gt;&gt; {
*
* }
* </pre>
*
* However since java doesn't have type inferencing we'll have to write often the full type of intermediate nodes,
* especially during tree construction. Thus it's recommended that you split the chain into a number of dummy classes,
* serving only to the purpuse of declaring the type chain:
*
* <pre>
* class MyTree extends TreeNode&lt;RootPayload, L1Payload, L1Tree&gt; {
* }
*
* class L1Tree extends TreeNode&lt;L1Payload, L2Payload, L2Tree&gt; {
* }
*
* class L2Tree extends TreeNode&lt;L2Payload, Void, NilTreeNode&gt; {
* }
* </pre>
*
* NOTE: you could keep the whole definition inside a single file using inner classes.
*
* @author marko
*
* @param <T>
* Type of the payload (or resource)
* @param <N>
* Type of the next level payload
* @param <C>
* Type of the next level node (another TreeNode)
*/
public class TreeNode<T, N, V, C extends TreeNode<N, ?, V, ?>> {
public static final String CHILDR_UNDER_LEAF = "cannot create children under leaf nodes";
private static final Log log = LogFactory.getLog(TreeNode.class); // NOPMD by marko on 11/24/08 5:02 PM
T resource;
List<C> children = new ArrayList<C>();
transient Class<? extends C> childNodeType = autoChildNodeType(getClass());
/**
* Default constructor doesn't set the payload. This is used internally to construct a node. We cannot use the
* <code>TreeNode(T resource)</code> constructor only because it would force any implementor of "alias" subclasses
* of TreeNode to provide a dummy implementation of the constructor calling <code>super(resource)</code>
*
* However the constructor is protected because users should only create objects through <code>addChild</code> or in
* alternative they should provide they own constructor ovverrides.
*/
protected TreeNode() {
// no action
}
/**
* However the constructor is protected because users should only create objects through <code>addChild</code> or in
* alternative they should provide they own constructor ovverrides.
*
* @param resource
* payload
*/
protected TreeNode(final T resource) {
this.resource = resource;
}
/**
* Call this method to add a child node.
*
* Only the payload is needed, the tree node will be constructed automatically and it will be returned.
*
* <pre>
* L1Child c1 = root.addChild(new L1Resource());
* c1.addChild(new L2Resource());
* c1.addChild(new L2Resource()).addChild(new L3Resource());
* </pre>
*
* @param resource
* payload
* @return the new node
*/
@SuppressWarnings("unchecked")
public C addChild(final N resource) {
try {
if (childNodeType.equals(NilTreeNode.class))
throw new IllegalStateException(CHILDR_UNDER_LEAF);
C test;
try {
test = childNodeType.newInstance();
test.setResource(resource);
} catch (InstantiationException e) {
/*
* handle the situation when someone wants to create a one argument constructor hiding the default
* constructor provided by the base class. Nodes should normally be constructed using addChild but we
* don't want to force users to do it, and if they do it they shouldn't be punished with obscure
* InstantiationException caused by our evil usage of reflection. Of course we should do much more here
* in order to be more robust in the general case.
*/
try {
test = (C) childNodeType.getConstructors()[0].newInstance(resource); // NOPMD
} catch (InvocationTargetException e1) {
throw new IllegalStateException(e1);
} catch (InstantiationException e1) {
throw new IllegalStateException(e1);
}
}
getChildren().add(test);
return test;
} catch (IllegalArgumentException e) {
throw new IllegalStateException(e);
} catch (SecurityException e) {
throw new IllegalStateException(e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
/**
* This method is especially useful for leaf nodes when you want to append several children to the same parent node,
* without having to declare a temporary variable to hold the parent node (which could have long and ugly type)
*
* @param resource
* @return the parent node
*/
public TreeNode<T, N, V, C> appendChild(final N resource) {
addChild(resource);
return this;
}
/**
* This method applies a visitor to all nodes in a breadth first fashon
*
* Currently null payloads are skipped
*
* @see Visitor
* @param visitor
*/
public void breadthFirst(final V visitor) {
final Queue<TreeNode<?, ?, V, ?>> queue = new LinkedList<TreeNode<?, ?, V, ?>>();
queue.add(this);
while (!queue.isEmpty()) {
final TreeNode<?, ?, V, ?> current = queue.remove();
log.info("visiting " + current);
current.accept(visitor);
queue.addAll(current.getChildren());
}
}
/**
* depth first.
*
* Currently null payloads are skipped
*
* @see Visitor
* @param Visitor
*/
public void depthFirst(final V visitor) {
baseDepthFirst(visitor);
}
public void baseDepthFirst(final V visitor) {
accept(visitor);
for (C el : children)
el.baseDepthFirst(visitor);
}
/**
* For the curious, this method takes care of retrieving the runtime type information for the tree node element
* declared for children nodes.
*
* Because of java type erasure, this is not strictly needed for making this kind of tree work, but it's needed if
* we want to enable people typing ugly long signatures and use type aliases as described in the class
* documentation; otherwise a cast exception would occur.
*
* Aargh, any serious language which permits type polymorphysm should have a type alias feature
*
* @param clazz
* the class object of this class
* @return the class object of the node element describing children
*/
@SuppressWarnings({ "unchecked", "rawtypes"})
protected Class<? extends C> autoChildNodeType(final Class<? extends TreeNode> clazz) {
final Type superType = clazz.getGenericSuperclass();
if (superType instanceof ParameterizedType) {
final ParameterizedType paramSuperType = (ParameterizedType) superType;
int argumentIndex;
if (paramSuperType.getRawType() == TreeNode.class)
argumentIndex = 3;
else
argumentIndex = 2;
final Type argument = (paramSuperType).getActualTypeArguments()[argumentIndex];
return getRawType(argument);
} else {
return autoChildNodeType((Class<? extends TreeNode>) superType);
}
}
@SuppressWarnings("unchecked")
protected <X> X getRawType(final Type type) {
if (type instanceof ParameterizedType)
return (X) ((ParameterizedType) type).getRawType();
return (X) type;
}
public List<C> getChildren() {
return children;
}
public void setChildren(final List<C> children) {
this.children = children;
}
public T getResource() {
return resource;
}
public void setResource(final T resource) {
this.resource = resource;
}
public void accept(final V dummy) {
log.fatal("should be ovverriden");
}
}