@Override
    public boolean equals(@Nullable Object obj) {
      if (obj == this) {
        return true;
      }
      if (!(obj instanceof EndpointPair)) {
        return false;
      }

      EndpointPair<?> other = (EndpointPair<?>)obj;
      if (isOrdered() != other.isOrdered()) {
        return false;
      }

      // Equivalent to the following simple implementation:
      // boolean condition1 = nodeU().equals(other.nodeU()) && nodeV().equals(other.nodeV());
      // boolean condition2 = nodeU().equals(other.nodeV()) && nodeV().equals(other.nodeU());
      // return condition1 || condition2;
      if (nodeU().equals(other.nodeU())) { // check condition1
        // Here's the tricky bit. We don't have to explicitly check for condition2 in this case.
        // Why? The second half of condition2 requires that nodeV equals other.nodeU.
        // We already know that nodeU equals other.nodeU. Combined with the earlier statement,
        // and the transitive property of equality, this implies that nodeU equals nodeV.
        // If nodeU equals nodeV, condition1 == condition2, so checking condition1 is sufficient.
        return nodeV().equals(other.nodeV());
      }
      return nodeU().equals(other.nodeV()) && nodeV().equals(other.nodeU()); // check condition2
    }
@Override
    public boolean equals(@Nullable Object obj)