/*
 * Created on May 8, 2008
 *
 * Copyright (c) 2008, The JUNG Authors 
 *
 * All rights reserved.
 *
 * This software is open-source under the BSD license; see either
 * "license.txt" or
 * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
 */
package edu.uci.ics.jung.graph;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.graph.util.Pair;

/**
 * An implementation of <code>Tree</code> in which each vertex has
 * &le; k children.  The value of 'k' is specified by the constructor
 * parameter.  A specific child (edge) can be retrieved directly by specifying the
 * index at which the child is located.  By default, new (child) vertices
 * are added at the lowest index available, if no index is specified.
 * 
 */
@SuppressWarnings("serial")
public class OrderedKAryTree<V, E> extends AbstractTypedGraph<V, E> implements Tree<V, E> 
{
    protected Map<E, Pair<V>> edge_vpairs;
    protected Map<V, VertexData> vertex_data;
    protected int height;
    protected V root;
    protected int order;
    
    /**
     * @param <V> the vertex type for the graph Supplier
     * @param <E> the edge type for the graph Supplier
     * @param order the maximum number of children ("k") that any vertex can have
     * @return a {@code Supplier} that creates an instance of this graph type.
     */
    public static <V,E> Supplier<DirectedGraph<V,E>> getFactory(final int order) {
        return new Supplier<DirectedGraph<V,E>> () {
            public DirectedGraph<V,E> get() {
                return new OrderedKAryTree<V,E>(order);
            }
        };
    }
    
    /**
     * Creates a new instance with the specified order (maximum number of children).
     * @param order the maximum number of children ("k") that any vertex can have
     */
    public OrderedKAryTree(int order)
    {
    	super(EdgeType.DIRECTED);
    	this.order = order;
    	this.height = -1;
    	this.edge_vpairs = new HashMap<E, Pair<V>>();
    	this.vertex_data = new HashMap<V, VertexData>();
    }
  
    /**
     * @param vertex the vertex whose number of children is to be returned
     * @return the number of children that {@code vertex} has
     * @see edu.uci.ics.jung.graph.Tree#getChildCount(java.lang.Object)
     */
    public int getChildCount(V vertex) {
        if (!containsVertex(vertex)) 
            return 0;
        List<E> edges = vertex_data.get(vertex).child_edges;
        if (edges == null)
        	return 0;
        int count = 0;
        for (E edge : edges)
            count += edge == null ? 0 : 1;
    
        return count;
    }
  
    /**
     * @param vertex the vertex whose child edge is to be returned
     * @param index the index of the edge to be returned
     * @return the child edge of {@code vertex} at index {@code index}, that is, 
     *     its <i>i</i>th child edge.
     */
    public E getChildEdge(V vertex, int index) 
    {
        if (!containsVertex(vertex)) 
        	return null;
        List<E> edges = vertex_data.get(vertex).child_edges;
        if (edges == null)
        	return null;
        return edges.get(index);
    }

    /**
     * @see edu.uci.ics.jung.graph.Tree#getChildEdges(java.lang.Object)
     */
    public Collection<E> getChildEdges(V vertex) 
    {
        if (!containsVertex(vertex)) 
        	return null;
        List<E> edges = vertex_data.get(vertex).child_edges;
        return edges == null ? Collections.<E>emptySet() : 
        	new ImmutableList.Builder<E>().addAll(edges).build();
    }
  
    /**
     * Returns an ordered list of {@code vertex}'s child vertices.
     * If there is no child in position i, then the list will contain
     * {@code null} in position i.  If {@code vertex} has no children
     * then the empty set will be returned.
     * @see edu.uci.ics.jung.graph.Tree#getChildren(java.lang.Object)
     */
    public Collection<V> getChildren(V vertex) 
    {
        if (!containsVertex(vertex)) 
            return null;
        List<E> edges = vertex_data.get(vertex).child_edges;
        if (edges == null)
        	return Collections.emptySet();
        Collection<V> children = new ArrayList<V>(order);
        for (E edge : edges)
            children.add(this.getOpposite(vertex, edge));
        return new ImmutableList.Builder<V>().addAll(children).build();
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Tree#getDepth(java.lang.Object)
     * @return the depth of the vertex in this tree, or -1 if the vertex is
     * not present in this tree
     */
    public int getDepth(V vertex) 
    {
        if (!containsVertex(vertex))
            return -1;
        return vertex_data.get(vertex).depth;
    }
  
    /**
     * Returns the height of the tree, or -1 if the tree is empty.
     * @see edu.uci.ics.jung.graph.Tree#getHeight()
     */
    public int getHeight() 
    {
        return height;
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Tree#getParent(java.lang.Object)
     */
    public V getParent(V vertex) 
    {
        if (!containsVertex(vertex))
            return null;
        else if (vertex.equals(root))
            return null;
        return edge_vpairs.get(vertex_data.get(vertex).parent_edge).getFirst();
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Tree#getParentEdge(java.lang.Object)
     */
    public E getParentEdge(V vertex) 
    {
        if (!containsVertex(vertex))
            return null;
        return vertex_data.get(vertex).parent_edge;
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Tree#getRoot()
     */
    public V getRoot() 
    {
        return root;
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Forest#getTrees()
     */
    public Collection<Tree<V, E>> getTrees() 
    {
        Collection<Tree<V, E>> forest = new ArrayList<Tree<V, E>>(1);
        forest.add(this);
        return forest;
    }
  
    /**
     * Adds the specified {@code child} vertex and edge {@code e} to the graph
     * with the specified parent vertex {@code parent}.  If {@code index} is 
     * greater than or equal to 0, then the child is placed at position
     * {@code index}; if it is less than 0, the child is placed at the lowest
     * available position; if it is greater than or equal to the order of this
     * tree, an exception is thrown.
     * 
     * @param e the edge to add
     * @param parent the source of the edge to be added
     * @param child the destination of the edge to be added
     * @param index the position at which e is to be added as a child of {@code parent}
     * @return {@code true} if the graph has been modified
     * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object)
     */
	public boolean addEdge(E e, V parent, V child, int index) 
    {
        if (e == null || child == null || parent == null)
            throw new IllegalArgumentException("Inputs may not be null");
    	if (!containsVertex(parent))
    		throw new IllegalArgumentException("Tree must already " +
    				"include parent: " + parent);
    	if (containsVertex(child))
    		throw new IllegalArgumentException("Tree must not already " +
    				"include child: " + child);
		if (parent.equals(child))
			throw new IllegalArgumentException("Input vertices must be distinct");
		if (index < 0 || index >= order)
		    throw new IllegalArgumentException("'index' must be in [0, [order-1]]");
    	
    	Pair<V> endpoints = new Pair<V>(parent, child);
    	if (containsEdge(e))
    		if (!endpoints.equals(edge_vpairs.get(e)))
    			throw new IllegalArgumentException("Tree already includes edge" + 
    					e + " with different endpoints " + edge_vpairs.get(e));
    		else
    			return false;

    	VertexData parent_data = vertex_data.get(parent);
    	List<E> outedges = parent_data.child_edges;
    	
    	if (outedges == null)
    	    outedges = new ArrayList<E>(this.order);

    	boolean edge_placed = false;
    	if (index >= 0)
    		if (outedges.get(index) != null)
        		throw new IllegalArgumentException("Parent " + parent + 
        				" already has a child at index " + index + " in this tree");
    		else
    			outedges.set(index, e);
    	for (int i = 0; i < order; i++)
    	{
    		if (outedges.get(i) == null)
    		{
    			outedges.set(i, e);
    			edge_placed = true;
    			break;
    		}
    	}
    	if (!edge_placed)
    		throw new IllegalArgumentException("Parent " + parent + " already" +
    				" has " + order + " children in this tree");
    	
    	// initialize VertexData for child; leave child's child_edges null for now
    	VertexData child_data = new VertexData(e, parent_data.depth + 1);
    	vertex_data.put(child, child_data);
    	
    	height = child_data.depth > height ? child_data.depth : height;
    	edge_vpairs.put(e, endpoints);
    	
    	return true;
    }

    /**
     * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object)
     */
	@Override
    public boolean addEdge(E e, V parent, V child)
	{
		return addEdge(e, parent, child, -1);
	}

    
    /**
     * @see edu.uci.ics.jung.graph.Graph#addEdge(java.lang.Object, java.lang.Object, java.lang.Object, edu.uci.ics.jung.graph.util.EdgeType)
     */
    @Override
    public boolean addEdge(E e, V v1, V v2, EdgeType edge_type) 
    {
    	this.validateEdgeType(edge_type);
    	return addEdge(e, v1, v2);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getDest(java.lang.Object)
     */
    public V getDest(E directed_edge) 
    {
        if (!containsEdge(directed_edge))
            return null;
        return edge_vpairs.get(directed_edge).getSecond();
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getEndpoints(java.lang.Object)
     */
    public Pair<V> getEndpoints(E edge) 
    {
        if (!containsEdge(edge))
            return null;
        return edge_vpairs.get(edge);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getInEdges(java.lang.Object)
     */
    public Collection<E> getInEdges(V vertex) 
    {
        if (!containsVertex(vertex))
            return null;
        else if (vertex.equals(root))
            return Collections.emptySet();
        else
            return Collections.singleton(getParentEdge(vertex));
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getOpposite(java.lang.Object, java.lang.Object)
     */
    @Override
    public V getOpposite(V vertex, E edge) 
    {
        if (!containsVertex(vertex) || !containsEdge(edge))
            return null;
        Pair<V> endpoints = edge_vpairs.get(edge);
        V v1 = endpoints.getFirst();
        V v2 = endpoints.getSecond();
        return v1.equals(vertex) ? v2 : v1;
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getOutEdges(java.lang.Object)
     */
    public Collection<E> getOutEdges(V vertex) 
    {
        return getChildEdges(vertex);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getPredecessorCount(java.lang.Object)
     * @return 0 if <code>vertex</code> is the root, -1 if the vertex is 
     * not an element of this tree, and 1 otherwise
     */
    @Override
    public int getPredecessorCount(V vertex) 
    {
        if (!containsVertex(vertex))
            return -1;
        return vertex.equals(root) ? 0 : 1;
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getPredecessors(java.lang.Object)
     */
    public Collection<V> getPredecessors(V vertex) 
    {
        if (!containsVertex(vertex))
            return null;
        if (vertex.equals(root))
            return Collections.emptySet();
        return Collections.singleton(getParent(vertex));
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getSource(java.lang.Object)
     */
    public V getSource(E directed_edge) 
    {
        if (!containsEdge(directed_edge))
            return null;
        return edge_vpairs.get(directed_edge).getFirst();
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getSuccessorCount(java.lang.Object)
     */
    @Override
    public int getSuccessorCount(V vertex) 
    {
        return getChildCount(vertex);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#getSuccessors(java.lang.Object)
     */
    public Collection<V> getSuccessors(V vertex) 
    {
        return getChildren(vertex);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#inDegree(java.lang.Object)
     */
    @Override
    public int inDegree(V vertex) 
    {
        if (!containsVertex(vertex))
            return 0;
        if (vertex.equals(root))
            return 0;
        return 1;
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#isDest(java.lang.Object, java.lang.Object)
     */
    public boolean isDest(V vertex, E edge) 
    {
        if (!containsEdge(edge) || !containsVertex(vertex))
            return false;
        return edge_vpairs.get(edge).getSecond().equals(vertex);
    }
  
    /**
     * Returns <code>true</code> if <code>vertex</code> is a leaf of this tree,
     * i.e., if it has no children.
     * @param vertex the vertex to be queried
     * @return <code>true</code> if <code>outDegree(vertex)==0</code>
     */
    public boolean isLeaf(V vertex)
    {
        if (!containsVertex(vertex))
            return false;
        return outDegree(vertex) == 0;
    }
    
    /**
     * Returns true iff <code>v1</code> is the parent of <code>v2</code>.
     * Note that if <code>v2</code> is the root and <code>v1</code> is <code>null</code>,
     * this method returns <code>true</code>.
     * 
     * @see edu.uci.ics.jung.graph.Graph#isPredecessor(java.lang.Object, java.lang.Object)
     */
    @Override
    public boolean isPredecessor(V v1, V v2) 
    {
        if (!containsVertex(v2))
            return false;
        return getParent(v2).equals(v1);
    }
  
    /**
     * Returns <code>true</code> if <code>vertex</code> is a leaf of this tree,
     * i.e., if it has no children.
     * @param vertex the vertex to be queried
     * @return <code>true</code> if <code>outDegree(vertex)==0</code>
     */
    public boolean isRoot(V vertex)
    {
        if (root == null)
            return false;
        return root.equals(vertex);
    }
    
    /**
     * @see edu.uci.ics.jung.graph.Graph#isSource(java.lang.Object, java.lang.Object)
     */
    public boolean isSource(V vertex, E edge) 
    {
        if (!containsEdge(edge) || !containsVertex(vertex))
            return false;
        return edge_vpairs.get(edge).getFirst().equals(vertex);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#isSuccessor(java.lang.Object, java.lang.Object)
     */
    @Override
    public boolean isSuccessor(V v1, V v2) 
    {
        if (!containsVertex(v2))
            return false;
        if (containsVertex(v1))
            return getParent(v1).equals(v2);
        return isLeaf(v2) && v1 == null;
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Graph#outDegree(java.lang.Object)
     */
    @Override
    public int outDegree(V vertex) 
    {
        if (!containsVertex(vertex))
            return 0;
        List<E> out_edges = vertex_data.get(vertex).child_edges;
        if (out_edges == null)
        	return 0;
        int degree = 0;
        for (E e : out_edges)
        	degree += (e == null) ? 0 : 1;
        return degree;
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#addEdge(java.lang.Object, java.util.Collection)
     */
    @Override
    @SuppressWarnings("unchecked")
	public boolean addEdge(E edge, Collection<? extends V> vertices, EdgeType edge_type) 
    {
        if (edge == null || vertices == null)
            throw new IllegalArgumentException("inputs may not be null");
        if (vertices.size() != 2)
            throw new IllegalArgumentException("'vertices' must contain " +
            		"exactly 2 distinct vertices");
    	this.validateEdgeType(edge_type);
		Pair<V> endpoints;
		if (vertices instanceof Pair)
			endpoints = (Pair<V>)vertices;
		else
			endpoints = new Pair<V>(vertices);
		V v1 = endpoints.getFirst();
		V v2 = endpoints.getSecond();
		if (v1.equals(v2))
			throw new IllegalArgumentException("Input vertices must be distinct");
		return addEdge(edge, v1, v2);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#addVertex(java.lang.Object)
     */
    public boolean addVertex(V vertex) 
    {
		if(root == null) 
		{
			this.root = vertex;
			vertex_data.put(vertex, new VertexData(null, 0));
			this.height = 0;
			return true;
		} 
		else 
		{
			throw new UnsupportedOperationException("Unless you are setting " +
					"the root, use addEdge() or addChild()");
		}
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#isIncident(java.lang.Object, java.lang.Object)
     */
    @Override
    public boolean isIncident(V vertex, E edge) 
    {
        if (!containsVertex(vertex) || !containsEdge(edge))
            return false;
        return edge_vpairs.get(edge).contains(vertex);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#isNeighbor(java.lang.Object, java.lang.Object)
     */
    @Override
    public boolean isNeighbor(V v1, V v2) 
    {
    	if (!containsVertex(v1) || !containsVertex(v2))
    		return false;
    	return getNeighbors(v1).contains(v2);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#containsEdge(java.lang.Object)
     */
    public boolean containsEdge(E edge) 
    {
    	return edge_vpairs.containsKey(edge);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#containsVertex(java.lang.Object)
     */
    public boolean containsVertex(V vertex) 
    {
    	return vertex_data.containsKey(vertex);
    }
  
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#findEdge(java.lang.Object, java.lang.Object)
     */
    @Override
    public E findEdge(V v1, V v2) 
    {
        if (!containsVertex(v1) || !containsVertex(v2))
            return null;
    	VertexData v1_data = vertex_data.get(v1);
    	if (edge_vpairs.get(v1_data.parent_edge).getFirst().equals(v2))
    		return v1_data.parent_edge;
    	List<E> edges = v1_data.child_edges;
    	if (edges == null)
    		return null;
    	for (E edge : edges)
    		if (edge != null && edge_vpairs.get(edge).getSecond().equals(v2))
    			return edge;
    	return null;
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#findEdgeSet(java.lang.Object, java.lang.Object)
     */
    @Override
    public Collection<E> findEdgeSet(V v1, V v2) 
    {
    	E edge = findEdge(v1, v2);
    	if (edge == null)
    		return Collections.emptySet();
    	else
    		return Collections.singleton(edge);
    }
  
    /**
     * Returns the child of <code>vertex</code> at position <code>index</code> 
     * in this tree, or <code>null</code> if it has no child at that position.
     * @param vertex the vertex to query
     * @param index the index of the child to return
     * @return the child of <code>vertex</code> at position <code>index</code> 
     * in this tree, or <code>null</code> if it has no child at that position
     * @throws ArrayIndexOutOfBoundsException if <code>index</code> is not in 
     * the range {@code [0, order-1]}
     */
    public V getChild(V vertex, int index)
    {
        if (index < 0 || index >= order)
            throw new ArrayIndexOutOfBoundsException(index + " is not in [0, order-1]");
        if (!containsVertex(vertex))
            return null;
        List<E> edges = vertex_data.get(vertex).child_edges;
        if (edges == null)
        	return null;
        E edge = edges.get(index);
        return edge == null ? null : edge_vpairs.get(edge).getSecond();
    }
    
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#getEdgeCount()
     */
    public int getEdgeCount() 
    {
    	return edge_vpairs.size();
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#getEdges()
     */
    public Collection<E> getEdges() 
    {
    	return new ImmutableSet.Builder<E>().addAll(edge_vpairs.keySet()).build();
    	//CollectionUtils.unmodifiableCollection(edge_vpairs.keySet());
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentCount(java.lang.Object)
     */
    @Override
    public int getIncidentCount(E edge) 
    {
    	return 2;  // all tree edges have 2 incident vertices
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentEdges(java.lang.Object)
     */
    public Collection<E> getIncidentEdges(V vertex) 
    {
    	if (!containsVertex(vertex))
    		return null;
    	ArrayList<E> edges = new ArrayList<E>(order+1);
    	VertexData v_data = vertex_data.get(vertex);
    	if (v_data.parent_edge != null)
    		edges.add(v_data.parent_edge);
    	if (v_data.child_edges != null)
    	{
    		for (E edge : v_data.child_edges)
    			if (edge != null)
    				edges.add(edge);
    	}
    	if (edges.isEmpty())
    		return Collections.emptySet();
    	return Collections.unmodifiableCollection(edges);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#getIncidentVertices(java.lang.Object)
     */
    @Override
    public Collection<V> getIncidentVertices(E edge) 
    {
    	return edge_vpairs.get(edge);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#getNeighborCount(java.lang.Object)
     */
    @Override
    public int getNeighborCount(V vertex) 
    {
        if (!containsVertex(vertex))
            return 0;
    	return (vertex.equals(root) ? 0 : 1) + this.getChildCount(vertex);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#getNeighbors(java.lang.Object)
     */
    public Collection<V> getNeighbors(V vertex) 
    {
    	if (!containsVertex(vertex))
    		return null;
    	ArrayList<V> vertices = new ArrayList<V>(order+1);
    	VertexData v_data = vertex_data.get(vertex);
    	if (v_data.parent_edge != null)
    		vertices.add(edge_vpairs.get(v_data.parent_edge).getFirst());
    	if (v_data.child_edges != null)
    	{
    		for (E edge : v_data.child_edges)
    			if (edge != null)
    				vertices.add(edge_vpairs.get(edge).getSecond());
    	}
    	if (vertices.isEmpty())
    		return Collections.emptySet();
    	return Collections.unmodifiableCollection(vertices);
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#getVertexCount()
     */
    public int getVertexCount() 
    {
    	return vertex_data.size();
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#getVertices()
     */
    public Collection<V> getVertices() 
    {
      return new ImmutableSet.Builder<V>().addAll(vertex_data.keySet()).build();
      //CollectionUtils.unmodifiableCollection(vertex_data.keySet());
    }
  
    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#removeEdge(java.lang.Object)
     */
    public boolean removeEdge(E edge) 
    {
    	if (!containsEdge(edge))
    		return false;
    	
    	removeVertex(edge_vpairs.get(edge).getSecond());
    	edge_vpairs.remove(edge);
    	
    	return true;
    }

    /**
     * @see edu.uci.ics.jung.graph.Hypergraph#removeVertex(java.lang.Object)
     */
    public boolean removeVertex(V vertex) 
    {
    	if (!containsVertex(vertex))
    		return false;
    	
    	// recursively remove all of vertex's children
		for(V v : getChildren(vertex))
			removeVertex(v);

		E parent_edge = getParentEdge(vertex);
		edge_vpairs.remove(parent_edge);
		List<E> edges = vertex_data.get(vertex).child_edges;
		if (edges != null)
			for (E edge : edges)
				edge_vpairs.remove(edge);
		vertex_data.remove(vertex);
		
		return true;
    }
	
	protected class VertexData
	{
	    List<E> child_edges;
		E parent_edge;
		int depth;
		
		protected VertexData(E parent_edge, int depth)
		{
			this.parent_edge = parent_edge;
			this.depth = depth;
		}
	}

	@Override
	public boolean addEdge(E edge, Pair<? extends V> endpoints, EdgeType edgeType) 
	{
	    if (edge == null || endpoints == null)
	        throw new IllegalArgumentException("inputs must not be null");
		return addEdge(edge, endpoints.getFirst(), endpoints.getSecond(), edgeType);
	}
}
