/** @see Graph#addEdge(Object, Object, Object) */
  public boolean addEdge(V sourceVertex, V targetVertex, E e) {
    if (e == null) {
      throw new NullPointerException();
    } else if (containsEdge(e)) {
      return false;
    }

    assertVertexExist(sourceVertex);
    assertVertexExist(targetVertex);

    if (!allowingMultipleEdges && containsEdge(sourceVertex, targetVertex)) {
      return false;
    }

    if (!allowingLoops && sourceVertex.equals(targetVertex)) {
      throw new IllegalArgumentException(LOOPS_NOT_ALLOWED);
    }

    IntrusiveEdge intrusiveEdge = createIntrusiveEdge(e, sourceVertex, targetVertex);

    edgeMap.put(e, intrusiveEdge);
    specifics.addEdgeToTouchingVertices(e);

    return true;
  }
  /** @see Graph#addEdge(Object, Object) */
  public E addEdge(V sourceVertex, V targetVertex) {
    assertVertexExist(sourceVertex);
    assertVertexExist(targetVertex);

    if (!allowingMultipleEdges && containsEdge(sourceVertex, targetVertex)) {
      return null;
    }

    if (!allowingLoops && sourceVertex.equals(targetVertex)) {
      throw new IllegalArgumentException(LOOPS_NOT_ALLOWED);
    }

    E e = edgeFactory.createEdge(sourceVertex, targetVertex);

    if (containsEdge(e)) { // this restriction should stay!

      return null;
    } else {
      IntrusiveEdge intrusiveEdge = createIntrusiveEdge(e, sourceVertex, targetVertex);

      edgeMap.put(e, intrusiveEdge);
      specifics.addEdgeToTouchingVertices(e);

      return e;
    }
  }
  /** @see Graph#vertexSet() */
  public Set<V> vertexSet() {
    if (unmodifiableVertexSet == null) {
      unmodifiableVertexSet = Collections.unmodifiableSet(specifics.getVertexSet());
    }

    return unmodifiableVertexSet;
  }
  public void union(AbstractBaseGraph<V, E> g2) {
    if (!(this instanceof DirectedGraph<?, ?> && g2 instanceof DirectedGraph<?, ?>)) {
      return;
    }
    Set<V> vertexSet = g2.vertexSet();
    for (V vertex : vertexSet) {
      if (!containsVertex(vertex)) addVertex(vertex);
    }

    for (V vertex : vertexSet) {
      Set<E> edgeSet = g2.specifics.outgoingEdgesOf(vertex);
      for (E edge : edgeSet) {
        V sourceVertex = getEdgeSource(edge);
        V targetVertex = getEdgeTarget(edge);
        if (!allowingMultipleEdges && containsEdge(sourceVertex, targetVertex)) {
          continue;
        }

        if (!allowingLoops && sourceVertex.equals(targetVertex)) {
          continue;
        }

        if (containsEdge(edge)) {
          continue;
        } else {
          IntrusiveEdge intrusiveEdge = createIntrusiveEdge(edge, sourceVertex, targetVertex);

          edgeMap.put(edge, intrusiveEdge);
          specifics.addEdgeToTouchingVertices(edge);
        }
      }
    }
  }
  /** @see Graph#removeEdge(Object) */
  public boolean removeEdge(E e) {
    if (containsEdge(e)) {
      specifics.removeEdgeFromTouchingVertices(e);
      edgeMap.remove(e);

      return true;
    } else {
      return false;
    }
  }
  /** @see Graph#removeEdge(Object, Object) */
  public E removeEdge(V sourceVertex, V targetVertex) {
    E e = getEdge(sourceVertex, targetVertex);

    if (e != null) {
      specifics.removeEdgeFromTouchingVertices(e);
      edgeMap.remove(e);
    }

    return e;
  }
  /** @see Graph#addVertex(Object) */
  public boolean addVertex(V v) {
    if (v == null) {
      throw new NullPointerException();
    } else if (containsVertex(v)) {
      return false;
    } else {
      specifics.addVertex(v);

      return true;
    }
  }
  /** @see Graph#removeVertex(Object) */
  public boolean removeVertex(V v) {
    if (containsVertex(v)) {
      Set<E> touchingEdgesList = edgesOf(v);

      // cannot iterate over list - will cause
      // ConcurrentModificationException
      removeAllEdges(new ArrayList<E>(touchingEdgesList));

      specifics.getVertexSet().remove(v); // remove the vertex itself

      return true;
    } else {
      return false;
    }
  }
 /** @see DirectedGraph#outgoingEdgesOf(Object) */
 public Set<E> outgoingEdgesOf(V vertex) {
   return specifics.outgoingEdgesOf(vertex);
 }
 /** @see DirectedGraph#outDegreeOf(Object) */
 public int outDegreeOf(V vertex) {
   return specifics.outDegreeOf(vertex);
 }
 /** @see DirectedGraph#incomingEdgesOf(Object) */
 public Set<E> incomingEdgesOf(V vertex) {
   return specifics.incomingEdgesOf(vertex);
 }
 /** @see Graph#getEdge(Object, Object) */
 public E getEdge(V sourceVertex, V targetVertex) {
   return specifics.getEdge(sourceVertex, targetVertex);
 }
 /** @see Graph#edgesOf(Object) */
 public Set<E> edgesOf(V vertex) {
   return specifics.edgesOf(vertex);
 }
 /** @see UndirectedGraph#degreeOf(Object) */
 public int degreeOf(V vertex) {
   return specifics.degreeOf(vertex);
 }
 /** @see Graph#containsVertex(Object) */
 public boolean containsVertex(V v) {
   return specifics.getVertexSet().contains(v);
 }
 /** @see DirectedGraph#inDegreeOf(Object) */
 public int inDegreeOf(V vertex) {
   return specifics.inDegreeOf(vertex);
 }
 /** @see Graph#getAllEdges(Object, Object) */
 public Set<E> getAllEdges(V sourceVertex, V targetVertex) {
   return specifics.getAllEdges(sourceVertex, targetVertex);
 }