Pure Danger Tech


navigation
home

Fun with generics and enums

25 Oct 2006

So, I dove head-first into generics and enums and all the glorious madness of JDK 5 this week. I wrote the little class below to create a parameterized tree node that is parameterized by the valid set of node types (an enumeration) and a valid set of property keys (also an enumeration).

Ran across a couple interesting things in the process. One was the way I needed to parameterize the Node class with an enum. All declared enums extend java.lang.Enum, which is defined as Enum<E extends Enum>! So, you need to declare each parameter with something like “P extends Enum<P>”.

I got extra crazy and decided to use an EnumMap for the node properties. EnumMap is a new implementation of Map that uses an enumeration as its key set. The actual implementation is ultra-efficient as it is just backed by an array of the value class type and setting the value just drops it in the ordinal value of the array. The one possible downside is that the EnumMap implementation can’t handle null keys (although this is probably a non-issue if you’re using an enumeration as your key set anyways).

Anyhow, the tricky thing about EnumMap is that you have to pass the Class for the key type in the constructor of the EnumMap. I couldn’t figure out any way to hide this when parameterizing my own Node class, so you have to do the same when constructing the Node, which kind of sucks from a usage point of view. Am I missing something?

So, a simple construction example would look like:

`So, I dove head-first into generics and enums and all the glorious madness of JDK 5 this week. I wrote the little class below to create a parameterized tree node that is parameterized by the valid set of node types (an enumeration) and a valid set of property keys (also an enumeration).

Ran across a couple interesting things in the process. One was the way I needed to parameterize the Node class with an enum. All declared enums extend java.lang.Enum, which is defined as Enum<E extends Enum>! So, you need to declare each parameter with something like “P extends Enum<P>”.

I got extra crazy and decided to use an EnumMap for the node properties. EnumMap is a new implementation of Map that uses an enumeration as its key set. The actual implementation is ultra-efficient as it is just backed by an array of the value class type and setting the value just drops it in the ordinal value of the array. The one possible downside is that the EnumMap implementation can’t handle null keys (although this is probably a non-issue if you’re using an enumeration as your key set anyways).

Anyhow, the tricky thing about EnumMap is that you have to pass the Class for the key type in the constructor of the EnumMap. I couldn’t figure out any way to hide this when parameterizing my own Node class, so you have to do the same when constructing the Node, which kind of sucks from a usage point of view. Am I missing something?

So, a simple construction example would look like:

`

I choice to force the type of the Node’s children to be from the same node type and property key type enumerations. I wavered over this choice as it makes it invalid to create a tree with node types or property types that span multiple enumerations. I can definitely see cases where that would be desirable, so this may be a bad choice. Time will tell.

Here’s the actual Node class:

import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;

/**
 * This class defines a generic tree node.  Each node has a type, properties, and children.  The 
 * node is parameterized with two Enumerations.  The enumeration T defines the set of valid node types.  
 * The enumeration P defines the set of valid property keys.  Using these annotations makes the 
 * Node class more specific in context of its use.
 *
 * @param <T> Enumeration defining the set of valid node types
 * @param <P> Enumeration defining the set of valid node property keys
 */
public class Node<T extends Enum<T>, P extends Enum<P>> {
    /** The type of the Node.  Choices are defined in the enumeration T. */
    private T type;
    
    /** The properties of the Node.  The valid keys are defined in the enumeration P. */
    private Map<P, Object> props;       // init in constructor based on P class
    
    /** The children of the Node.  The children must have the same key/property types as this node. */
    private List<Node<T,P>> children = new ArrayList<Node<T,P>>();
    
    /**
     * Construct a new Node.
     * @param type The type of the node, as defined in the enumeration T
     * @param propType The type of the property enumeration class.  This is needed to construct the 
     * internal EnumMap which holds the properties.
     */
    public Node(T type, Class<P> propType) {
        this.type = type;      
        this.props = new EnumMap<P, Object>(propType);
    }

    /**
     * Get the node type.
     * @return Node type, an instance of the T enumeration.
     */
    public T getType() {
        return type;
    }

    /**
     * Set the node type
     * @param type Node type, an instance of the T enumeration
     */
    public void setType(T type) {
        this.type = type;
    }
    
    /**
     * Get all properties.  The Map is keyed from the P property key enumeration 
     * and returns Objects as values.
     * @return Map of all properties
     */
    public Map<P, Object> getProperties() {
        return this.props;
    }
    
    /**
     * Set property with key in enumeration P and a value.
     * @param property Property key in enumeration P
     * @param value Value
     */
    public void setProperty(P property, Object value) {
        this.props.put(property, value);
    }
    
    /**
     * Get property with key in enumeration P.
     * @param property Property key in enumeration P
     * @return Value
     */
    public Object getProperty(P property) {
        return this.props.get(property);
    }
    
    /**
     * Get children, which are all nodes of the same type as this node
     * @return List of child Nodes, never null
     */
    public List<Node<T, P>> getChildren() {
        return this.children;
    }
    
    /**
     * Add a new child to this child.
     * @param child Child node of same types as this node
     */
    public void addChild(Node<T,P> child) {
        this.children.add(child);
    }
}