org.epochx.epox.Node.java Source code

Java tutorial

Introduction

Here is the source code for org.epochx.epox.Node.java

Source

/*
 * Copyright 2007-2013
 * Licensed under GNU Lesser General Public License
 * 
 * This file is part of EpochX: genetic programming software for research
 * 
 * EpochX is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * EpochX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with EpochX. If not, see <http://www.gnu.org/licenses/>.
 * 
 * The latest version is available from: http://www.epochx.org
 */
package org.epochx.epox;

import java.util.*;

import org.apache.commons.lang.*;
import org.epochx.tools.DataTypeUtils;

/**
 * A <code>Node</code> is a vertex in a tree structure which represents a program.
 * A node can be thought of as an expression in a computer programming language.
 * Evaluating a node will involve evaluating any children and optionally returning a 
 * value.
 * 
 * Subclasses of <code>Node</code> should ensure they call the superclass
 * constructor with all child nodes so information such as the arity of the
 * node can be maintained. Concrete subclasses must also implement
 * <code>evaluate()</code> to evaluate the expression represented by the tree.
 * Nodes which support mixed type arguments, or terminal nodes with no arguments
 * must also override the <code>getReturnType(Class&lt;?&gt;)</code> method to
 * indicate their return type. The <code>clone</code> and <code>newInstance</code>
 * methods are also heavily used, so implementations should ensure this classes 
 * implementations are sufficient or override as necessary.
 * 
 * @since 2.0
 */
public abstract class Node implements Cloneable {

    // TODO Consider renaming to EpoxNode

    private Node[] children;

    private Node parent;

    /**
     * Constructs a new <code>Node</code> with the given child nodes. The arity of 
     * the node will be the number of child nodes provided. The child nodes may 
     * be initially set here as <code>null</code> and replaced before evaluation. 
     * Terminal nodes are simply nodes with no children.
     * 
     * @param children child nodes to this node
     */
    public Node(Node... children) {
        setChildren(children);
    }

    /**
     * Subclasses should implement this method to perform some operation with
     * respect to its children and return a result. If there is no result 
     * (for example, because this node has a <code>Void</code> data-type), then 
     * <code>null</code> should be returned.
     * 
     * @return the result of evaluating the node tree rooted at this node
     */
    public abstract Object evaluate();

    /**
     * Returns a specific child by index
     * 
     * @param index the index of the child to be returned, valid indexes run
     *        from <code>0</code> to <code>getArity()-1</code>
     * @return the child node at the specified index
     */
    public Node getChild(int index) {
        return children[index];
    }

    /**
     * Returns the parent of this node or <code>null</code> if it is the root node
     * 
     * @return the node that this node is a child of
     */
    public Node getParent() {
        return parent;
    }

    /**
     * Returns an array of this node's children. Modifying this array will not
     * change the set of children, but modifying the nodes will alter the nodes
     * of the tree.
     * 
     * @return an array of this node's children
     */
    public Node[] getChildren() {
        return (Node[]) ArrayUtils.clone(children);
    }

    /**
     * Sets the child nodes of this node. Modifications to this array after 
     * being set will not modify the set of child nodes. The number of children
     * set here does not need to match the current arity.
     * 
     * @param children the nodes to set as children in order
     */
    public void setChildren(Node... children) {
        // Must be careful to maintain the integrity of parent
        this.children = new Node[children.length];

        int index = 0;
        for (Node child : children) {
            setChild(index++, child);
        }
    }

    /**
     * Returns the element at the specified position in the node tree, where
     * this node is considered to be the root - that is the 0th node. The tree
     * is traversed in pre-order (depth first).
     * 
     * @param n the index of the node to be returned
     * @return the node at the specified position in this node tree
     * @throws IndexOutOfBoundsException if <code>n</code> is out of range
     */
    public Node getNode(int n) {
        if (n >= 0) {
            return getNode(n, 0);
        } else {
            throw new IndexOutOfBoundsException("attempt to get node at negative index");
        }
    }

    /*
     * Recursive helper for the public getNode(int)
     */
    private Node getNode(int n, int current) {
        if (n == current) {
            return this;
        }

        Node node = null;
        for (Node child : children) {
            int childLength = child.length();

            // Only look at the subtree if it contains the right range of nodes.
            if (n <= childLength + current) {
                node = child.getNode(n, current + 1);
                if (node != null) {
                    break;
                }
            }

            current += childLength;
        }

        // If node is null now then the index did not exist within any children.
        if (node == null) {
            throw new IndexOutOfBoundsException("attempt to get node at index >= length");
        }

        return node;
    }

    /**
     * Replaces the node at the specified position in this node tree, where
     * this node is considered to be the root node - that is, the 0th node.
     * The tree is traversed in pre-order (depth first). It is not possible to
     * set the 0th node, since it does not make sense for an object to be able
     * to replace itself.
     * 
     * @param n the index of the node to replace
     * @param newNode the node to be stored at the specified position
     * @throws IndexOutOfBoundsException if <code>n</code> is out of range
     */
    public Node setNode(int n, Node newNode) {
        if (n > 0) {
            return setNode(n, newNode, 0);
        } else if (n == 0) {
            throw new IndexOutOfBoundsException("attempt to set node at index 0, cannot replace self");
        } else {
            throw new IndexOutOfBoundsException("attempt to set node at negative index");
        }
    }

    /*
     * Recursive helper for the public setNode(int, Node)
     */
    private Node setNode(int n, Node newNode, int current) {
        int arity = getArity();
        for (int i = 0; i < arity; i++) {
            if (current + 1 == n) {
                Node old = getChild(i);
                setChild(i, newNode);
                return old;
            }

            Node child = getChild(i);
            int childLength = child.length();

            // Only look at the subtree if it contains the right range of nodes
            if (n <= childLength + current) {
                return child.setNode(n, newNode, current + 1);
            }

            current += childLength;
        }

        throw new IndexOutOfBoundsException("attempt to set node at index >= length");
    }

    /**
     * Returns the index of the nth non-terminal node, where this node is 
     * considered to be the root - that is the 0th node. The tree's nodes are 
     * counted in pre-order (depth first) to locate the nth function, and return
     * its index within all nodes. Will throw an exception if the index is out 
     * of bounds, which will be the case for all indexes when called on a 
     * terminal node.
     * 
     * @param n the non-terminal to find the index of
     * @return the index of the nth non-terminal node
     * @throws IndexOutOfBoundsException if <code>n</code> is out of range
     */
    public int nthNonTerminalIndex(int n) {
        int index = nthNonTerminalIndex(n, 0, 0, this);

        if (index < 0) {
            throw new IndexOutOfBoundsException("attempt to get function node index at index out of range");
        }

        return index;
    }

    /*
     * Recursive helper function for nthNonTerminalIndex
     */
    private int nthNonTerminalIndex(int n, int functionCount, int nodeCount, Node current) {
        if (current.isNonTerminal() && (n == functionCount)) {
            return nodeCount;
        }

        int result = -1;
        for (Node child : current.children) {
            int noNodes = child.length();
            int noFunctions = child.countNonTerminals();

            // Only look at the subtree if it contains the right range of nodes
            if (n <= noFunctions + functionCount) {
                int childResult = nthNonTerminalIndex(n, functionCount + 1, nodeCount + 1, child);
                if (childResult != -1) {
                    return childResult;
                }
            }

            // Skip the correct number of nodes from the subtree
            functionCount += noFunctions;
            nodeCount += noNodes;
        }

        return result;
    }

    /**
     * Returns the index of the nth terminal node, where this node is considered
     * to be the root - that is the 0th node. The tree's nodes are counted in
     * pre-order (depth first) to locate the nth terminal, and return its index
     * within all nodes.
     * 
     * @param n the terminal to find the index of
     * @return the index of the nth terminal node
     * @throws IllegalArgumentException if <code>n</code> is out of bounds
     */
    public int nthTerminalIndex(int n) {
        int index = nthTerminalIndex(n, 0, 0, this);

        if (index < 0) {
            throw new IndexOutOfBoundsException("attempt to get terminal node index at index out of range");
        }

        return index;
    }

    /*
     * Recursive helper function for nthTerminalIndex
     */
    private int nthTerminalIndex(int n, int terminalCount, int nodeCount, Node current) {
        if (current.getArity() == 0) {
            if (n == terminalCount++) {
                return nodeCount;
            }
        }

        int result = -1;
        for (Node child : children) {
            int noNodes = child.length();
            int noTerminals = child.countTerminals();

            // Only look at the subtree if it contains the right range of nodes
            if (n <= noTerminals + terminalCount) {
                int childResult = nthTerminalIndex(n, terminalCount, nodeCount + 1, child);
                if (childResult != -1) {
                    return childResult;
                }
            }

            // Skip the correct number of nodes from the subtree
            terminalCount += noTerminals;
            nodeCount += noNodes;
        }

        return result;
    }

    /**
     * Retrieves all the nodes in the node tree at a specified depth from this
     * current node. This node is considered to be at depth zero.
     * 
     * @param depth the specified depth of the nodes to return
     * @return a <code>List</code> of all the nodes at the specified depth
     */
    public List<Node> nodesAtDepth(int depth) {
        List<Node> nodes = new ArrayList<Node>((depth + 1) * 3);
        if (depth >= 0) {
            nodesAtDepth(nodes, depth, 0);
        } else {
            throw new IndexOutOfBoundsException("attempt to get nodes at negative depth");
        }

        if (nodes.isEmpty()) {
            throw new IndexOutOfBoundsException("attempt to get nodes at depth greater than maximum depth");
        }

        return nodes;
    }

    /*
     * A helper function for nodesAtDepth(int), to recurse down the node
     * tree and populate the nodes array when at the correct depth.
     */
    private void nodesAtDepth(List<Node> nodes, int d, int current) {
        if (d == current) {
            nodes.add(this);
        } else {
            for (Node child : children) {
                // Get the nodes at the right depth down each branch
                child.nodesAtDepth(nodes, d, current + 1);
            }
        }
    }

    /**
     * Replaces the child node at the specified index with the given node
     * 
     * @param index the index of the child to replace, from <code>0</code> to
     *        <code>getArity()-1</code>
     * @param child the child node to be stored at the specified position
     */
    public void setChild(int index, Node child) {
        children[index] = child;

        if (child != null) {
            child.parent = this;
        }
    }

    /**
     * Returns the number of immediate children this <code>Node</code> has. This
     * is effectively the number of inputs the node has. A node with arity
     * zero, is considered to be a terminal node.
     * 
     * @return the number of children required by this node
     */
    public int getArity() {
        return children.length;
    }

    /**
     * Returns a count of the terminal nodes in this node tree
     * 
     * @return the number of terminal nodes in this node tree
     */
    public int countTerminals() {
        if (isTerminal()) {
            return 1;
        } else {
            int result = 0;
            for (int i = 0; i < getArity(); i++) {
                result += getChild(i).countTerminals();
            }
            return result;
        }
    }

    /**
     * Returns a count of the unique terminal nodes in the node tree below this
     * node
     * 
     * @return the number of unique terminal nodes in this node tree
     */
    public int countDistinctTerminals() {
        List<Node> terminals = listTerminals();

        // Remove duplicates.
        Set<Node> terminalHash = new HashSet<Node>(terminals);

        return terminalHash.size();
    }

    /**
     * Returns a list of all the terminal nodes in this node tree
     * 
     * @return a <code>List</code> of all the terminal nodes in this node tree
     */
    public List<Node> listTerminals() {
        List<Node> terminals = new ArrayList<Node>();

        int arity = getArity();
        if (isTerminal()) {
            terminals.add(this);
        } else {
            for (int i = 0; i < arity; i++) {
                terminals.addAll(getChild(i).listTerminals());
            }
        }
        return terminals;
    }

    /**
     * Returns a count of the non-terminal nodes in this node tree
     * 
     * @return the number of non-terminal nodes in this node tree
     */
    public int countNonTerminals() {
        if (isTerminal()) {
            return 0;
        } else {
            int result = 1;
            for (int i = 0; i < getArity(); i++) {
                result += getChild(i).countNonTerminals();
            }
            return result;
        }
    }

    /**
     * Returns a count of the unique non-terminal nodes in this node tree
     * 
     * @return the number of unique non-terminal nodes in this node tree
     */
    public int countDistinctNonTerminals() {
        List<Node> nonTerminals = listNonTerminals();

        // Remove duplicates. Cannot use equals because that compares children
        List<String> identifiers = new ArrayList<String>();
        for (Node f : nonTerminals) {
            String name = f.getIdentifier();
            if (!identifiers.contains(name)) {
                identifiers.add(name);
            }
        }

        return identifiers.size();
    }

    /**
     * Returns a list of all the non-terminal nodes in this node tree
     * 
     * @return a <code>List</code> of all the non-terminal nodes in this node tree
     */
    public List<Node> listNonTerminals() {
        List<Node> nonTerminals = new ArrayList<Node>();

        if (isNonTerminal()) {
            // Add this node as a function and search its child nodes.
            nonTerminals.add(this);

            for (int i = 0; i < getArity(); i++) {
                nonTerminals.addAll(getChild(i).listNonTerminals());
            }
        }

        return nonTerminals;
    }

    /**
     * Returns the depth of deepest node in the node tree, given that this node
     * is at depth zero
     * 
     * @return the depth of the deepest node in the node tree
     */
    public int depth() {
        return depth(this, 0, 0);
    }

    /*
     * A private helper function for depth() which recurses down the node
     * tree to determine the deepest node's depth
     */
    private int depth(Node rootNode, int currentDepth, int depth) {
        if (currentDepth > depth) {
            depth = currentDepth;
        }

        int arity = rootNode.getArity();
        if (arity > 0) {
            for (int i = 0; i < arity; i++) {
                Node childNode = rootNode.getChild(i);
                depth = depth(childNode, (currentDepth + 1), depth);
            }
        }
        return depth;
    }

    /**
     * Returns the number of nodes in the node tree
     * 
     * @return the number of nodes in the node tree
     */
    public int length() {
        return length(this, 0);
    }

    /*
     * A private recursive helper function for length() which traverses the
     * the node tree counting the number of nodes
     */
    private int length(Node rootNode, int length) {
        length++;

        int arity = rootNode.getArity();
        if (arity > 0) {
            for (int i = 0; i < arity; i++) {
                Node childNode = rootNode.getChild(i);
                length = length(childNode, length);
            }
        }
        return length;
    }

    /**
     * Should be implemented to return an indentifier for this node. For
     * functions, where this is effectively the function name, this would
     * normally be unique within the given problem.
     * 
     * @return a <code>String</code> identifier for this node.
     */
    public abstract String getIdentifier();

    /**
     * Returns the data-type of this node based on the child nodes that are
     * currently set. If any of this node's child nodes are currently
     * <code>null</code>, or their data-types are invalid, then the return type will
     * also be <code>null</code>.
     * 
     * @return the return type of this node or <code>null</code> if any of its
     *         children remain unset or are of an invalid data-type
     */
    public final Class<?> dataType() {
        Class<?>[] argTypes = new Class<?>[getArity()];
        for (int i = 0; i < getArity(); i++) {
            Node child = getChild(i);
            if (child != null) {
                argTypes[i] = child.dataType();
            } else {
                return null;
            }
        }
        return dataType(argTypes);
    }

    /**
     * Returns this node's return type given the provided input data-types.
     * The default implementation for a non-terminal is that the node will
     * support the closure requirement - the return type will be the widest of
     * the input types, or <code>null</code> if they are not compatible. The default
     * return value for a terminal is <code>Void</code>. Mixed type non-terminal
     * nodes and most terminal nodes should override this method. If the input
     * types are invalid then <code>null</code> should be returned.
     * 
     * @param inputTypes the set of input data-types for which to get the return
     *        type.
     * @return the return type of this node given the provided input types, or
     *         null if the set of input types is invalid.
     */
    public Class<?> dataType(Class<?>... inputTypes) {
        if (isTerminal()) {
            return Void.class;
        } else {
            // Either the widest type or null if not valid
            return DataTypeUtils.getSuper(inputTypes);
        }
    }

    /**
     * Returns <code>true</code> if this node has an arity of greater than
     * <code>0</code>.
     * 
     * @return <code>true</code> if this node is a non-terminal, and <code>false</code>
     *         otherwise.
     */
    public boolean isNonTerminal() {
        return (getArity() > 0);
    }

    /**
     * Returns <code>true</code> if this node has an arity of <code>0</code>
     * 
     * @return <code>true</code> if this node is a terminal, and <code>false</code>
     *         otherwise
     */
    public boolean isTerminal() {
        return (getArity() == 0);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        int result = getIdentifier().hashCode();
        for (final Node child : children) {
            if (child != null) {
                result = 37 * result + child.hashCode();
            }
        }
        return result;
    }

    /**
     * Creates a deep copy of this node and its node tree. Each child node will
     * be cloned. Use this method for copying a whole node tree. Some
     * implementations of this class may need to override this method.
     * 
     * @return a copy of this <code>Node</code> and its children
     */
    @Override
    public Node clone() {
        try {
            Node clone = (Node) super.clone();

            clone.children = children.clone();
            for (int i = 0; i < children.length; i++) {
                clone.children[i] = children[i]; // TODO Don't think we need
                // this line.
                if (clone.children[i] != null) {
                    clone.children[i] = clone.children[i].clone();
                }
            }

            return clone;
        } catch (final CloneNotSupportedException e) {
            assert false;
            // This shouldn't ever happen - if it does then everythings going to
            // blow up anyway.
        }
        return null;
    }

    /**
     * Constructs a new instance of this node-type. Rather than copying the node
     * tree, this node is copied without children. In the case of many terminal
     * nodes the bahaviour of this method will be the same as the clone method.
     * By default this method will simply return a new instance of the same type
     * in the same manner as clone, but with all children removed. Note that
     * there is no requirement for an implementation to return a different
     * instance.
     * 
     * @return a copy of this <code>Node</code> with all children removed
     */
    public Node newInstance() {
        try {
            Node n = (Node) super.clone();
            n.children = new Node[children.length];
            return n;
        } catch (final CloneNotSupportedException e) {
            assert false;
        }

        return null;
    }

    /**
     * Compares an this node to another object for equality. Two nodes may be
     * considered equal if they have equal arity, equal identifiers, and their
     * children are also equal (and in the same order). Some nodes may wish to
     * enforce a stricter contract.
     * 
     * @param obj {@inheritDoc}
     * @return {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        boolean equal = true;

        if (obj instanceof Node) {
            Node n = (Node) obj;

            if (n.getArity() != getArity()) {
                equal = false;
            } else if (!getIdentifier().equals(n.getIdentifier())) {
                equal = false;
            } else {
                for (int i = 0; (i < n.getArity()) && equal; i++) {
                    Node thatChild = n.getChild(i);
                    Node thisChild = getChild(i);

                    equal = ObjectUtils.equals(thisChild, thatChild);
                }
            }
        } else {
            equal = false;
        }
        return equal;
    }

    /**
     * Returns a string representation of this node. The default implementation
     * is output of the form:
     * 
     * <pre>
     * identifier(children)
     * </pre>
     * 
     * where <code>identifier</code> is the node's identifier as returned by
     * <code>getIdentifier</code>, and <code>children</code> is a space separated list
     * of child nodes, according to their <code>toString</code> representation.
     * 
     * @return a string representation of this node and its children
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder(getIdentifier());
        builder.append('(');
        for (int i = 0, n = children.length; i < n; i++) {
            Node c = children[i];
            if (i != 0) {
                builder.append(' ');
            }

            if (c == null) {
                builder.append('X');
            } else {
                builder.append(c.toString());
            }
        }
        builder.append(')');
        return builder.toString();
    }

}