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. * *
 * class MyTree extends TreeNode<RootPayload, L1Payload, TreeNode<L1Payload, L2Payload, TreeNode<L2Payload, Void, NilTreeNode>>> {
 * 
 * }
 * 
* * 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: * *
 * class MyTree extends TreeNode<RootPayload, L1Payload, L1Tree> {
 * }
 * 
 * class L1Tree extends TreeNode<L1Payload, L2Payload, L2Tree> {
 * }
 * 
 * class L2Tree extends TreeNode<L2Payload, Void, NilTreeNode> {
 * }
 * 
* * NOTE: you could keep the whole definition inside a single file using inner classes. * * @author marko * * @param * Type of the payload (or resource) * @param * Type of the next level payload * @param * Type of the next level node (another TreeNode) */ public class TreeNode> { 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 children = new ArrayList(); transient Class childNodeType = autoChildNodeType(getClass()); /** * Default constructor doesn't set the payload. This is used internally to construct a node. We cannot use the * TreeNode(T resource) constructor only because it would force any implementor of "alias" subclasses * of TreeNode to provide a dummy implementation of the constructor calling super(resource) * * However the constructor is protected because users should only create objects through addChild 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 addChild 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. * *
	 * L1Child c1 = root.addChild(new L1Resource());
	 * c1.addChild(new L2Resource());
	 * c1.addChild(new L2Resource()).addChild(new L3Resource());
	 * 
* * @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 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> queue = new LinkedList>(); queue.add(this); while (!queue.isEmpty()) { final TreeNode 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 autoChildNodeType(final Class 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) superType); } } @SuppressWarnings("unchecked") protected X getRawType(final Type type) { if (type instanceof ParameterizedType) return (X) ((ParameterizedType) type).getRawType(); return (X) type; } public List getChildren() { return children; } public void setChildren(final List 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"); } }