253 lines
7.6 KiB
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<RootPayload, L1Payload, TreeNode<L1Payload, L2Payload, TreeNode<L2Payload, Void, NilTreeNode>>> {
|
|
*
|
|
* }
|
|
* </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<RootPayload, L1Payload, L1Tree> {
|
|
* }
|
|
*
|
|
* class L1Tree extends TreeNode<L1Payload, L2Payload, L2Tree> {
|
|
* }
|
|
*
|
|
* class L2Tree extends TreeNode<L2Payload, Void, NilTreeNode> {
|
|
* }
|
|
* </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");
|
|
}
|
|
|
|
}
|