/** @see Graph#getAllEdges(Object, Object) */
    public Set<E> getAllEdges(V sourceVertex, V targetVertex) {
      Set<E> edges = null;

      if (containsVertex(sourceVertex) && containsVertex(targetVertex)) {
        edges = new ArrayUnenforcedSet<E>();

        Iterator<E> iter = getEdgeContainer(sourceVertex).vertexEdges.iterator();

        while (iter.hasNext()) {
          E e = iter.next();

          boolean equalStraight =
              sourceVertex.equals(getEdgeSource(e)) && targetVertex.equals(getEdgeTarget(e));

          boolean equalInverted =
              sourceVertex.equals(getEdgeTarget(e)) && targetVertex.equals(getEdgeSource(e));

          if (equalStraight || equalInverted) {
            edges.add(e);
          }
        }
      }

      return edges;
    }
  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 AbstractBaseGraph#removeEdgeFromTouchingVertices(Edge) */
    public void removeEdgeFromTouchingVertices(E e) {
      V source = getEdgeSource(e);
      V target = getEdgeTarget(e);

      getEdgeContainer(source).removeEdge(e);

      if (!source.equals(target)) {
        getEdgeContainer(target).removeEdge(e);
      }
    }
  /** @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#getEdge(Object, Object) */
    public E getEdge(V sourceVertex, V targetVertex) {
      if (containsVertex(sourceVertex) && containsVertex(targetVertex)) {
        Iterator<E> iter = getEdgeContainer(sourceVertex).vertexEdges.iterator();

        while (iter.hasNext()) {
          E e = iter.next();

          boolean equalStraight =
              sourceVertex.equals(getEdgeSource(e)) && targetVertex.equals(getEdgeTarget(e));

          boolean equalInverted =
              sourceVertex.equals(getEdgeTarget(e)) && targetVertex.equals(getEdgeSource(e));

          if (equalStraight || equalInverted) {
            return e;
          }
        }
      }

      return null;
    }
  private V findPath(V root) {
    Set<V> used = new HashSet<>();
    Queue<V> q = new ArrayDeque<>();

    // Expand graph back from its contracted state
    path.clear();
    contracted.clear();

    graph.vertexSet().forEach(vertex -> contracted.put(vertex, vertex));

    used.add(root);
    q.add(root);

    while (!q.isEmpty()) {
      V v = q.remove();

      for (E e : graph.edgesOf(v)) {
        V to = graph.getEdgeSource(e);

        if (to.equals(v)) {
          to = graph.getEdgeTarget(e);
        }

        if ((contracted.get(v).equals(contracted.get(to))) || to.equals(match.get(v))) {
          continue;
        }

        // Check whether we've hit a 'blossom'
        if ((to.equals(root)) || ((match.containsKey(to)) && (path.containsKey(match.get(to))))) {
          V stem = lowestCommonAncestor(v, to);

          Set<V> blossom = new HashSet<>();

          markPath(v, to, stem, blossom);
          markPath(to, v, stem, blossom);

          graph
              .vertexSet()
              .stream()
              .filter(i -> contracted.containsKey(i) && blossom.contains(contracted.get(i)))
              .forEach(
                  i -> {
                    contracted.put(i, stem);
                    if (!used.contains(i)) {
                      used.add(i);
                      q.add(i);
                    }
                  });

          // Check whether we've had hit a loop (of even length (!) presumably)
        } else if (!path.containsKey(to)) {
          path.put(to, v);

          if (!match.containsKey(to)) {
            return to;
          }

          to = match.get(to);

          used.add(to);
          q.add(to);
        }
      }
    }
    return null;
  }