/*
* Copyright (C) 2016 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.graph;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.graph.GraphConstants.NOT_AVAILABLE_ON_UNDIRECTED;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import com.google.common.collect.Iterators;
import com.google.common.collect.UnmodifiableIterator;
import com.google.errorprone.annotations.Immutable;
import org.checkerframework.checker.nullness.qual.Nullable;
An immutable pair representing the two endpoints of an edge in a graph. The EndpointPair
of a directed edge is an ordered pair of nodes (source()
and target()
). The EndpointPair
of an undirected edge is an unordered pair of nodes (nodeU()
and nodeV()
). The edge is a self-loop if, and only if, the two endpoints are equal.
Author: James Sexton Since: 20.0
/**
* An immutable pair representing the two endpoints of an edge in a graph. The {@link EndpointPair}
* of a directed edge is an ordered pair of nodes ({@link #source()} and {@link #target()}). The
* {@link EndpointPair} of an undirected edge is an unordered pair of nodes ({@link #nodeU()} and
* {@link #nodeV()}).
*
* <p>The edge is a self-loop if, and only if, the two endpoints are equal.
*
* @author James Sexton
* @since 20.0
*/
@Beta
@Immutable(containerOf = {"N"})
public abstract class EndpointPair<N> implements Iterable<N> {
private final N nodeU;
private final N nodeV;
private EndpointPair(N nodeU, N nodeV) {
this.nodeU = checkNotNull(nodeU);
this.nodeV = checkNotNull(nodeV);
}
Returns an EndpointPair
representing the endpoints of a directed edge. /** Returns an {@link EndpointPair} representing the endpoints of a directed edge. */
public static <N> EndpointPair<N> ordered(N source, N target) {
return new Ordered<N>(source, target);
}
Returns an EndpointPair
representing the endpoints of an undirected edge. /** Returns an {@link EndpointPair} representing the endpoints of an undirected edge. */
public static <N> EndpointPair<N> unordered(N nodeU, N nodeV) {
// Swap nodes on purpose to prevent callers from relying on the "ordering" of an unordered pair.
return new Unordered<N>(nodeV, nodeU);
}
Returns an EndpointPair
representing the endpoints of an edge in graph
. /** Returns an {@link EndpointPair} representing the endpoints of an edge in {@code graph}. */
static <N> EndpointPair<N> of(Graph<?> graph, N nodeU, N nodeV) {
return graph.isDirected() ? ordered(nodeU, nodeV) : unordered(nodeU, nodeV);
}
Returns an EndpointPair
representing the endpoints of an edge in network
. /** Returns an {@link EndpointPair} representing the endpoints of an edge in {@code network}. */
static <N> EndpointPair<N> of(Network<?, ?> network, N nodeU, N nodeV) {
return network.isDirected() ? ordered(nodeU, nodeV) : unordered(nodeU, nodeV);
}
If this EndpointPair
isOrdered()
, returns the node which is the source. Throws: - UnsupportedOperationException – if this
EndpointPair
is not ordered
/**
* If this {@link EndpointPair} {@link #isOrdered()}, returns the node which is the source.
*
* @throws UnsupportedOperationException if this {@link EndpointPair} is not ordered
*/
public abstract N source();
If this EndpointPair
isOrdered()
, returns the node which is the target. Throws: - UnsupportedOperationException – if this
EndpointPair
is not ordered
/**
* If this {@link EndpointPair} {@link #isOrdered()}, returns the node which is the target.
*
* @throws UnsupportedOperationException if this {@link EndpointPair} is not ordered
*/
public abstract N target();
If this EndpointPair
isOrdered()
returns the source()
; otherwise, returns an arbitrary (but consistent) endpoint of the origin edge. /**
* If this {@link EndpointPair} {@link #isOrdered()} returns the {@link #source()}; otherwise,
* returns an arbitrary (but consistent) endpoint of the origin edge.
*/
public final N nodeU() {
return nodeU;
}
Returns the node adjacent
to nodeU()
along the origin edge. If this EndpointPair
isOrdered()
, this is equal to target()
. /**
* Returns the node {@link #adjacentNode(Object) adjacent} to {@link #nodeU()} along the origin
* edge. If this {@link EndpointPair} {@link #isOrdered()}, this is equal to {@link #target()}.
*/
public final N nodeV() {
return nodeV;
}
Returns the node that is adjacent to node
along the origin edge. Throws: - IllegalArgumentException – if this
EndpointPair
does not contain node
/**
* Returns the node that is adjacent to {@code node} along the origin edge.
*
* @throws IllegalArgumentException if this {@link EndpointPair} does not contain {@code node}
*/
public final N adjacentNode(Object node) {
if (node.equals(nodeU)) {
return nodeV;
} else if (node.equals(nodeV)) {
return nodeU;
} else {
throw new IllegalArgumentException("EndpointPair " + this + " does not contain node " + node);
}
}
Returns true
if this EndpointPair
is an ordered pair (i.e. represents the endpoints of a directed edge). /**
* Returns {@code true} if this {@link EndpointPair} is an ordered pair (i.e. represents the
* endpoints of a directed edge).
*/
public abstract boolean isOrdered();
/** Iterates in the order {@link #nodeU()}, {@link #nodeV()}. */
@Override
public final UnmodifiableIterator<N> iterator() {
return Iterators.forArray(nodeU, nodeV);
}
Two ordered EndpointPair
s are equal if their source()
and target()
are equal. Two unordered EndpointPair
s are equal if they contain the same nodes. An ordered EndpointPair
is never equal to an unordered EndpointPair
. /**
* Two ordered {@link EndpointPair}s are equal if their {@link #source()} and {@link #target()}
* are equal. Two unordered {@link EndpointPair}s are equal if they contain the same nodes. An
* ordered {@link EndpointPair} is never equal to an unordered {@link EndpointPair}.
*/
@Override
public abstract boolean equals(@Nullable Object obj);
The hashcode of an ordered EndpointPair
is equal to Objects.hashCode(source(),
target())
. The hashcode of an unordered EndpointPair
is equal to
nodeU().hashCode() + nodeV().hashCode()
. /**
* The hashcode of an ordered {@link EndpointPair} is equal to {@code Objects.hashCode(source(),
* target())}. The hashcode of an unordered {@link EndpointPair} is equal to {@code
* nodeU().hashCode() + nodeV().hashCode()}.
*/
@Override
public abstract int hashCode();
private static final class Ordered<N> extends EndpointPair<N> {
private Ordered(N source, N target) {
super(source, target);
}
@Override
public N source() {
return nodeU();
}
@Override
public N target() {
return nodeV();
}
@Override
public boolean isOrdered() {
return true;
}
@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;
}
return source().equals(other.source()) && target().equals(other.target());
}
@Override
public int hashCode() {
return Objects.hashCode(source(), target());
}
@Override
public String toString() {
return "<" + source() + " -> " + target() + ">";
}
}
private static final class Unordered<N> extends EndpointPair<N> {
private Unordered(N nodeU, N nodeV) {
super(nodeU, nodeV);
}
@Override
public N source() {
throw new UnsupportedOperationException(NOT_AVAILABLE_ON_UNDIRECTED);
}
@Override
public N target() {
throw new UnsupportedOperationException(NOT_AVAILABLE_ON_UNDIRECTED);
}
@Override
public boolean isOrdered() {
return false;
}
@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 int hashCode() {
return nodeU().hashCode() + nodeV().hashCode();
}
@Override
public String toString() {
return "[" + nodeU() + ", " + nodeV() + "]";
}
}
}