[graphchart] New API for graph diagrams providing

GitOrigin-RevId: 97b4a5adfd76c0b598a768fa6d2b78d8095834f7
This commit is contained in:
Alexander Bashkirov
2021-10-04 07:13:14 +00:00
committed by intellij-monorepo-bot
parent 48a6e67125
commit 3d81756b29
11 changed files with 1461 additions and 0 deletions
@@ -0,0 +1,29 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.graph;
import com.intellij.openapi.application.ApplicationManager;
import org.jetbrains.annotations.NotNull;
public abstract class GraphFactory {
public static @NotNull GraphFactory getInstance() {
return ApplicationManager.getApplication().getService(GraphFactory.class);
}
/** Returns a {@link NetworkBuilder} for building directed networks. */
public abstract @NotNull NetworkBuilder<Object, Object> directedNetwork();
/** Returns a {@link NetworkBuilder} for building undirected networks. */
public abstract @NotNull NetworkBuilder<Object, Object> undirectedNetwork();
/**
* Returns a {@link NetworkBuilder} initialized with all properties queryable from {@code
* network}.
*
* <p>The "queryable" properties are those that are exposed through the {@link Network} interface,
* such as {@link Network#isDirected()}. Other properties, such as {@link
* NetworkBuilder#expectedNodeCount(int)}, are not set in the new builder.
*/
public abstract <N, E> @NotNull NetworkBuilder<N, E> from(@NotNull Network<N, E> network);
}
@@ -16,5 +16,6 @@
<orderEntry type="library" name="aalto-xml" level="project" />
<orderEntry type="module" module-name="intellij.platform.util.xmlDom" />
<orderEntry type="library" name="automaton" level="project" />
<orderEntry type="library" name="Guava" level="project" />
</component>
</module>
@@ -0,0 +1,256 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.graph.impl;
import com.intellij.util.graph.ElementOrder;
import com.intellij.util.graph.EndpointPair;
import com.intellij.util.graph.Graph;
import com.intellij.util.graph.MutableNetwork;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
public final class GraphAdapter {
private GraphAdapter() { }
public static <N> EndpointPair<N> wrapEndpointsPair(com.google.common.graph.EndpointPair<N> endpoints) {
if (endpoints.isOrdered()) {
return EndpointPair.ordered(endpoints.source(), endpoints.target());
}
else {
return EndpointPair.unordered(endpoints.nodeU(), endpoints.nodeV());
}
}
public static <N> com.google.common.graph.EndpointPair<N> unwrapEndpointsPair(EndpointPair<N> endpoints) {
if (endpoints.isOrdered()) {
return com.google.common.graph.EndpointPair.ordered(endpoints.source(), endpoints.target());
}
else {
return com.google.common.graph.EndpointPair.unordered(endpoints.nodeU(), endpoints.nodeV());
}
}
public static <T> ElementOrder<T> wrapOrder(com.google.common.graph.ElementOrder<T> order) {
switch (order.type()) {
case STABLE:
return ElementOrder.stable();
case INSERTION:
return ElementOrder.insertion();
case SORTED:
return ElementOrder.sorted(order.comparator());
case UNORDERED:
default:
return ElementOrder.unordered();
}
}
public static <T> com.google.common.graph.ElementOrder<T> unwrapOrder(ElementOrder<T> order) {
switch (order.type()) {
case STABLE:
return com.google.common.graph.ElementOrder.stable();
case INSERTION:
return com.google.common.graph.ElementOrder.insertion();
case SORTED:
return com.google.common.graph.ElementOrder.sorted(order.comparator());
case UNORDERED:
default:
return com.google.common.graph.ElementOrder.unordered();
}
}
public static <N, E> MutableNetwork<N, E> wrapNetwork(com.google.common.graph.MutableNetwork<N, E> delegate) {
return new MutableNetwork<N, E>() {
@Override
public boolean addNode(N n) {
return delegate.addNode(n);
}
@Override
public boolean addEdge(N n, N n1, E e) {
return delegate.addEdge(n, n1, e);
}
@Override
public boolean addEdge(EndpointPair<N> endpoints, E edge) {
return delegate.addEdge(unwrapEndpointsPair(endpoints), edge);
}
@Override
public boolean removeNode(N n) {
return delegate.removeNode(n);
}
@Override
public boolean removeEdge(E e) {
return delegate.removeEdge(e);
}
@Override
public Set<N> nodes() {
return delegate.nodes();
}
@Override
public Set<E> edges() {
return delegate.edges();
}
@Override
public Graph<N> asGraph() {
return new Graph<N>() {
@Override
public @NotNull Collection<N> getNodes() {
return nodes();
}
@Override
public @NotNull Iterator<N> getIn(N n) {
return predecessors(n).iterator();
}
@Override
public @NotNull Iterator<N> getOut(N n) {
return successors(n).iterator();
}
};
}
@Override
public boolean isDirected() {
return delegate.isDirected();
}
@Override
public boolean allowsParallelEdges() {
return delegate.allowsParallelEdges();
}
@Override
public boolean allowsSelfLoops() {
return delegate.allowsSelfLoops();
}
@Override
public ElementOrder<N> nodeOrder() {
return wrapOrder(delegate.nodeOrder());
}
@Override
public ElementOrder<E> edgeOrder() {
return wrapOrder(delegate.edgeOrder());
}
@Override
public Set<N> adjacentNodes(N n) {
return delegate.adjacentNodes(n);
}
@Override
public Set<N> predecessors(N n) {
return delegate.predecessors(n);
}
@Override
public Set<N> successors(N n) {
return delegate.successors(n);
}
@Override
public Set<E> incidentEdges(N n) {
return delegate.incidentEdges(n);
}
@Override
public Set<E> inEdges(N n) {
return delegate.inEdges(n);
}
@Override
public Set<E> outEdges(N n) {
return delegate.outEdges(n);
}
@Override
public int degree(N n) {
return delegate.degree(n);
}
@Override
public int inDegree(N n) {
return delegate.inDegree(n);
}
@Override
public int outDegree(N n) {
return delegate.outDegree(n);
}
@Override
public EndpointPair<N> incidentNodes(E e) {
return wrapEndpointsPair(delegate.incidentNodes(e));
}
@Override
public Set<E> adjacentEdges(E e) {
return delegate.adjacentEdges(e);
}
@Override
public Set<E> edgesConnecting(N n, N n1) {
return delegate.edgesConnecting(n, n1);
}
@Override
public Optional<E> edgeConnecting(N n, N n1) {
return delegate.edgeConnecting(n, n1);
}
@Override
public E edgeConnectingOrNull(N n, N n1) {
return delegate.edgeConnectingOrNull(n, n1);
}
@Override
public Set<E> edgesConnecting(EndpointPair<N> endpoints) {
return delegate.edgesConnecting(unwrapEndpointsPair(endpoints));
}
@Override
public Optional<E> edgeConnecting(EndpointPair<N> endpoints) {
return delegate.edgeConnecting(unwrapEndpointsPair(endpoints));
}
@Override
public @Nullable E edgeConnectingOrNull(EndpointPair<N> endpoints) {
return delegate.edgeConnectingOrNull(unwrapEndpointsPair(endpoints));
}
@Override
public boolean hasEdgeConnecting(N n, N n1) {
return delegate.hasEdgeConnecting(n, n1);
}
@Override
public boolean hasEdgeConnecting(EndpointPair<N> endpoints) {
return delegate.hasEdgeConnecting(unwrapEndpointsPair(endpoints));
}
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
@Override
public boolean equals(@Nullable Object o) {
return delegate.equals(o);
}
@Override
public int hashCode() {
return delegate.hashCode();
}
};
}
}
@@ -0,0 +1,30 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.graph.impl;
import com.intellij.util.graph.GraphFactory;
import com.intellij.util.graph.Network;
import com.intellij.util.graph.NetworkBuilder;
import org.jetbrains.annotations.NotNull;
public class GraphFactoryImpl extends GraphFactory {
@Override
public @NotNull NetworkBuilder<Object, Object> directedNetwork() {
return new NetworkBuilderImpl<>(true);
}
@Override
public @NotNull NetworkBuilder<Object, Object> undirectedNetwork() {
return new NetworkBuilderImpl<>(false);
}
@Override
public @NotNull <N, E> NetworkBuilder<N, E> from(@NotNull Network<N, E> network) {
return new NetworkBuilderImpl<N, E>(network.isDirected())
.allowsParallelEdges(network.allowsParallelEdges())
.allowsSelfLoops(network.allowsSelfLoops())
.nodeOrder(network.nodeOrder())
.edgeOrder(network.edgeOrder());
}
}
@@ -0,0 +1,29 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.util.graph.impl;
import com.intellij.util.graph.MutableNetwork;
import com.intellij.util.graph.NetworkBuilder;
public class NetworkBuilderImpl<N, E> extends NetworkBuilder<N, E> {
protected NetworkBuilderImpl(boolean directed) {
super(directed);
}
@Override
public <N1 extends N, E1 extends E> MutableNetwork<N1, E1> build() {
com.google.common.graph.NetworkBuilder<Object, Object> guavaBuilder =
myIsDirected ? com.google.common.graph.NetworkBuilder.directed()
: com.google.common.graph.NetworkBuilder.undirected();
guavaBuilder
.allowsParallelEdges(myDoAllowParallelEdges)
.allowsSelfLoops(myDoAllowSelfLoops)
.edgeOrder(GraphAdapter.unwrapOrder(myEdgeOrder))
.nodeOrder(GraphAdapter.unwrapOrder(myNodeOrder));
if (myExpectedEdgeCount.isPresent()) guavaBuilder.expectedEdgeCount(myExpectedEdgeCount.getAsInt());
if (myExpectedNodeCount.isPresent()) guavaBuilder.expectedNodeCount(myExpectedNodeCount.getAsInt());
return GraphAdapter.wrapNetwork(guavaBuilder.build());
}
}
@@ -339,6 +339,8 @@
<applicationService serviceInterface="com.intellij.featureStatistics.ProductivityFeaturesRegistry"
serviceImplementation="com.intellij.featureStatistics.ProductivityFeaturesRegistryImpl"/>
<applicationService serviceInterface="com.intellij.util.graph.GraphFactory"
serviceImplementation="com.intellij.util.graph.impl.GraphFactoryImpl"/>
<applicationService serviceInterface="com.intellij.util.graph.GraphAlgorithms"
serviceImplementation="com.intellij.util.graph.impl.GraphAlgorithmsImpl"/>
@@ -0,0 +1,173 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* 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.intellij.util.graph;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* <p><b>NOTE:</b> The class is a full copy of the {@code com.google.common.graph.EndpointPair}.
* We like Guava's implementation, but we cannot use Guava in our API because
* we need additional abstraction layer on our side.</p>
*
* <h3>The description from Guava sources:</h3>
* <p>
* Used to represent the order of elements in a data structure that supports different options for
* iteration order guarantees.
*
* @author Joshua O'Madadhain
*/
public final class ElementOrder<T> {
private final Type myType;
@SuppressWarnings("Immutable") // Hopefully the comparator provided is immutable!
private final @Nullable Comparator<T> myComparator;
/**
* The type of ordering that this object specifies.
*
* <ul>
* <li>UNORDERED: no order is guaranteed.
* <li>STABLE: ordering is guaranteed to follow a pattern that won't change between releases.
* Some methods may have stronger guarantees.
* <li>INSERTION: insertion ordering is guaranteed.
* <li>SORTED: ordering according to a supplied comparator is guaranteed.
* </ul>
*/
public enum Type {
UNORDERED,
STABLE,
INSERTION,
SORTED
}
private ElementOrder(Type type, @Nullable Comparator<T> comparator) {
myType = Objects.requireNonNull(type);
myComparator = comparator;
assert ((type == Type.SORTED) == (comparator != null));
}
/**
* Returns an instance which specifies that no ordering is guaranteed.
*/
public static <S> ElementOrder<S> unordered() {
return new ElementOrder<>(Type.UNORDERED, null);
}
/**
* Returns an instance which specifies that ordering is guaranteed to be always be the same across
* iterations, and across releases. Some methods may have stronger guarantees.
*
* <p>This instance is only useful in combination with {@code incidentEdgeOrder}, e.g. {@code
* graphBuilder.incidentEdgeOrder(ElementOrder.stable())}.
*
* <h3>In combination with {@code incidentEdgeOrder}</h3>
*
* <p>{@code incidentEdgeOrder(ElementOrder.stable())} guarantees the ordering of the returned
* collections of the following methods:
*
* <ul>
* <li>For {@link Graph}:
* <ul>
* <li>{@code edges()}: Stable order
* <li>{@code adjacentNodes(node)}: Connecting edge insertion order
* <li>{@code predecessors(node)}: Connecting edge insertion order
* <li>{@code successors(node)}: Connecting edge insertion order
* <li>{@code incidentEdges(node)}: Edge insertion order
* </ul>
* <li>For {@link Network}:
* <ul>
* <li>{@code adjacentNodes(node)}: Stable order
* <li>{@code predecessors(node)}: Connecting edge insertion order
* <li>{@code successors(node)}: Connecting edge insertion order
* <li>{@code incidentEdges(node)}: Stable order
* <li>{@code inEdges(node)}: Edge insertion order
* <li>{@code outEdges(node)}: Edge insertion order
* <li>{@code adjacentEdges(edge)}: Stable order
* <li>{@code edgesConnecting(nodeU, nodeV)}: Edge insertion order
* </ul>
* </ul>
*/
public static <S> ElementOrder<S> stable() {
return new ElementOrder<>(Type.STABLE, null);
}
/**
* Returns an instance which specifies that insertion ordering is guaranteed.
*/
public static <S> ElementOrder<S> insertion() {
return new ElementOrder<>(Type.INSERTION, null);
}
/**
* Returns an instance which specifies that the natural ordering of the elements is guaranteed.
*/
public static <S extends Comparable<? super S>> ElementOrder<S> natural() {
return new ElementOrder<>(Type.SORTED, Comparator.<S>naturalOrder());
}
/**
* Returns an instance which specifies that the ordering of the elements is guaranteed to be
* determined by {@code comparator}.
*/
public static <S> ElementOrder<S> sorted(Comparator<S> comparator) {
return new ElementOrder<S>(Type.SORTED, Objects.requireNonNull(comparator));
}
/**
* Returns the type of ordering used.
*/
public Type type() {
return myType;
}
/**
* Returns the {@link Comparator} used.
*
* @throws UnsupportedOperationException if comparator is not defined
*/
public Comparator<T> comparator() {
if (myComparator != null) {
return myComparator;
}
throw new UnsupportedOperationException("This ordering does not define a comparator.");
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ElementOrder)) {
return false;
}
ElementOrder<?> other = (ElementOrder<?>)obj;
return (myType == other.myType) && Objects.equals(myComparator, other.myComparator);
}
@Override
public int hashCode() {
return Objects.hash(myType, myComparator);
}
@Override
public String toString() {
return "ElementOrder{" + "myType=" + myType + ", myComparator=" + myComparator + '}';
}
}
@@ -0,0 +1,263 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* 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.intellij.util.graph;
import com.intellij.util.UnmodifiableIterator;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Objects;
/**
* <b>NOTE:</b> The class is a full copy of the {@code com.google.common.graph.EndpointPair}.
* We like Guava's implementation, but we cannot use Guava in our API because
* we need additional abstraction layer on our side.
*
* <p>The description from Guava sources:
*
* <p>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
*/
public abstract class EndpointPair<N> implements Iterable<N> {
private final N myNodeU;
private final N myNodeV;
private EndpointPair(N nodeU, N nodeV) {
myNodeU = Objects.requireNonNull(nodeU);
myNodeV = Objects.requireNonNull(nodeV);
}
/**
* Returns an {@link EndpointPair} representing the endpoints of a directed edge.
*/
public static <N> EndpointPair<N> ordered(N source, N target) {
return new Ordered<>(source, target);
}
/**
* 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<>(nodeV, nodeU);
}
/**
* 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 unordered(nodeU, nodeV);
}
/**
* 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 {@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 {@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 {@link EndpointPair} {@link #isOrdered()} returns the {@link #source()}; otherwise,
* returns an arbitrary (but consistent) endpoint of the origin edge.
*/
public final N nodeU() {
return myNodeU;
}
/**
* 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 myNodeV;
}
/**
* 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(myNodeU)) {
return myNodeV;
}
else if (node.equals(myNodeV)) {
return myNodeU;
}
else {
throw new IllegalArgumentException("EndpointPair " + this + " does not contain node " + node);
}
}
/**
* 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 new UnmodifiableIterator<>(Arrays.asList(myNodeU, myNodeV).iterator());
}
/**
* 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 {@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.hash(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 graph");
}
@Override
public N target() {
throw new UnsupportedOperationException("Not available on undirected graph");
}
@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() + "]";
}
}
}
@@ -0,0 +1,108 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* 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.intellij.util.graph;
/**
* <p><b>NOTE:</b> The class is a full copy of the {@code com.google.common.graph.EndpointPair}.
* We like Guava's implementation, but we cannot use Guava in our API because
* we need additional abstraction layer on our side.</p>
*
* <h3>The description from Guava sources:</h3>
*
* A subinterface of {@link Network} which adds mutation methods. When mutation is not required,
* users should prefer the {@link Network} interface.
*
* @author James Sexton
* @author Joshua O'Madadhain
* @param <N> Node parameter type
* @param <E> Edge parameter type
*/
public interface MutableNetwork<N, E> extends Network<N, E> {
/**
* Adds {@code node} if it is not already present.
*
* <p><b>Nodes must be unique</b>, just as {@code Map} keys must be. They must also be non-null.
*
* @return {@code true} if the network was modified as a result of this call
*/
boolean addNode(N node);
/**
* Adds {@code edge} connecting {@code nodeU} to {@code nodeV}.
*
* <p>If the graph is directed, {@code edge} will be directed in this graph; otherwise, it will be
* undirected.
*
* <p><b>{@code edge} must be unique to this graph</b>, just as a {@code Map} key must be. It must
* also be non-null.
*
* <p>If {@code nodeU} and {@code nodeV} are not already present in this graph, this method will
* silently {@link #addNode(Object) add} {@code nodeU} and {@code nodeV} to the graph.
*
* <p>If {@code edge} already connects {@code nodeU} to {@code nodeV} (in the specified order if
* this network {@link #isDirected()}, else in any order), then this method will have no effect.
*
* @return {@code true} if the network was modified as a result of this call
* @throws IllegalArgumentException if {@code edge} already exists in the graph and does not
* connect {@code nodeU} to {@code nodeV}
* @throws IllegalArgumentException if the introduction of the edge would violate {@link
* #allowsParallelEdges()} or {@link #allowsSelfLoops()}
*/
boolean addEdge(N nodeU, N nodeV, E edge);
/**
* Adds {@code edge} connecting {@code endpoints}. In an undirected network, {@code edge} will
* also connect {@code nodeV} to {@code nodeU}.
*
* <p>If this graph is directed, {@code edge} will be directed in this graph; if it is undirected,
* {@code edge} will be undirected in this graph.
*
* <p>If this graph is directed, {@code endpoints} must be ordered.
*
* <p><b>{@code edge} must be unique to this graph</b>, just as a {@code Map} key must be. It must
* also be non-null.
*
* <p>If either or both endpoints are not already present in this graph, this method will silently
* {@link #addNode(Object) add} each missing endpoint to the graph.
*
* <p>If {@code edge} already connects an endpoint pair equal to {@code endpoints}, then this
* method will have no effect.
*
* @return {@code true} if the network was modified as a result of this call
* @throws IllegalArgumentException if {@code edge} already exists in the graph and connects some
* other endpoint pair that is not equal to {@code endpoints}
* @throws IllegalArgumentException if the introduction of the edge would violate {@link
* #allowsParallelEdges()} or {@link #allowsSelfLoops()}
* @throws IllegalArgumentException if the endpoints are unordered and the graph is directed
*/
boolean addEdge(EndpointPair<N> endpoints, E edge);
/**
* Removes {@code node} if it is present; all edges incident to {@code node} will also be removed.
*
* @return {@code true} if the network was modified as a result of this call
*/
boolean removeNode(N node);
/**
* Removes {@code edge} from this network, if it is present.
*
* @return {@code true} if the network was modified as a result of this call
*/
boolean removeEdge(E edge);
}
@@ -0,0 +1,414 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* 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.intellij.util.graph;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.Set;
/**
* <p><b>NOTE:</b> The class is a full copy of the {@code com.google.common.graph.EndpointPair}.
* We like Guava's implementation, but we cannot use Guava in our API because
* we need additional abstraction layer on our side.</p>
*
* <h3>The description from Guava sources:</h3>
* <p>
* An interface for <a
* href="https://en.wikipedia.org/wiki/Graph_(discrete_mathematics)">graph</a>-structured data,
* whose edges are unique objects.
*
* <p>A graph is composed of a set of nodes and a set of edges connecting pairs of nodes.
*
* <h3>Capabilities</h3>
*
* <p>{@code Network} supports the following use cases (<a
* href="https://github.com/google/guava/wiki/GraphsExplained#definitions">definitions of
* terms</a>):
*
* <ul>
* <li>directed graphs
* <li>undirected graphs
* <li>graphs that do/don't allow parallel edges
* <li>graphs that do/don't allow self-loops
* <li>graphs whose nodes/edges are insertion-ordered, sorted, or unordered
* <li>graphs whose edges are unique objects
* </ul>
*
* <h3>Building a {@code Network}</h3>
*
* <p>The implementation classes that {@code common.graph} provides are not public, by design. To
* create an instance of one of the built-in implementations of {@code Network}, use the {@code
* com.google.common.graph.NetworkBuilder} class:
*
* <pre>{@code
* final var graph = NetworkBuilder.directed().build();
* }</pre>
*
* <p>The Guava User Guide has <a
* href="https://github.com/google/guava/wiki/GraphsExplained#building-graph-instances">more
* information on (and examples of) building graphs</a>.
*
* <h3>Additional documentation</h3>
*
* <p>See the Guava User Guide for the {@code common.graph} package (<a
* href="https://github.com/google/guava/wiki/GraphsExplained">"Graphs Explained"</a>) for
* additional documentation, including:
*
* <ul>
* <li><a
* href="https://github.com/google/guava/wiki/GraphsExplained#equals-hashcode-and-graph-equivalence">
* {@code equals()}, {@code hashCode()}, and graph equivalence</a>
* <li><a href="https://github.com/google/guava/wiki/GraphsExplained#synchronization">
* Synchronization policy</a>
* <li><a href="https://github.com/google/guava/wiki/GraphsExplained#notes-for-implementors">Notes
* for implementors</a>
* </ul>
*
* @author James Sexton
* @author Joshua O'Madadhain
* @param <N> Node parameter type
* @param <E> Edge parameter type
*/
public interface Network<N, E> {
/* ------------------------------------------------------------------------------------------- */
//region Network-level accessors
/**
* Returns all nodes in this network, in the order specified by {@link #nodeOrder()}.
*/
Set<N> nodes();
/**
* Returns all edges in this network, in the order specified by {@link #edgeOrder()}.
*/
Set<E> edges();
/**
* Returns a live view of this network as a {@link Graph}. The resulting {@link Graph} will have
* an edge connecting node A to node B if this {@link Network} has an edge connecting A to B.
*
* <p>If this network {@link #allowsParallelEdges() allows parallel edges}, parallel edges will be
* treated as if collapsed into a single edge. For example, the {@link #degree(Object)} of a node
* in the {@link Graph} view may be less than the degree of the same node in this {@link Network}.
*/
Graph<N> asGraph();
//endregion
/* ------------------------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------------------------- */
//region Network properties
/**
* Returns true if the edges in this network are directed. Directed edges connect a {@link
* EndpointPair#source() source node} to a {@link EndpointPair#target() target node}, while
* undirected edges connect a pair of nodes to each other.
*/
boolean isDirected();
/**
* Returns true if this network allows parallel edges. Attempting to add a parallel edge to a
* network that does not allow them will throw an {@link IllegalArgumentException}.
*/
boolean allowsParallelEdges();
/**
* Returns true if this network allows self-loops (edges that connect a node to itself).
* Attempting to add a self-loop to a network that does not allow them will throw an {@link
* IllegalArgumentException}.
*/
boolean allowsSelfLoops();
/**
* Returns the order of iteration for the elements of {@link #nodes()}.
*/
ElementOrder<N> nodeOrder();
/**
* Returns the order of iteration for the elements of {@link #edges()}.
*/
ElementOrder<E> edgeOrder();
//endregion
/* ------------------------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------------------------- */
//region Element-level accessors
/**
* Returns the nodes which have an incident edge in common with {@code node} in this network.
*
* <p>This is equal to the union of {@link #predecessors(Object)} and {@link #successors(Object)}.
*
* @throws IllegalArgumentException if {@code node} is not an element of this network
*/
Set<N> adjacentNodes(N node);
/**
* Returns all nodes in this network adjacent to {@code node} which can be reached by traversing
* {@code node}'s incoming edges <i>against</i> the direction (if any) of the edge.
*
* <p>In an undirected network, this is equivalent to {@link #adjacentNodes(Object)}.
*
* @throws IllegalArgumentException if {@code node} is not an element of this network
*/
Set<N> predecessors(N node);
/**
* Returns all nodes in this network adjacent to {@code node} which can be reached by traversing
* {@code node}'s outgoing edges in the direction (if any) of the edge.
*
* <p>In an undirected network, this is equivalent to {@link #adjacentNodes(Object)}.
*
* <p>This is <i>not</i> the same as "all nodes reachable from {@code node} by following outgoing
* edges".
*
* @throws IllegalArgumentException if {@code node} is not an element of this network
*/
Set<N> successors(N node);
/**
* Returns the edges whose {@link #incidentNodes(Object) incident nodes} in this network include
* {@code node}.
*
* <p>This is equal to the union of {@link #inEdges(Object)} and {@link #outEdges(Object)}.
*
* @throws IllegalArgumentException if {@code node} is not an element of this network
*/
Set<E> incidentEdges(N node);
/**
* Returns all edges in this network which can be traversed in the direction (if any) of the edge
* to end at {@code node}.
*
* <p>In a directed network, an incoming edge's {@link EndpointPair#target()} equals {@code node}.
*
* <p>In an undirected network, this is equivalent to {@link #incidentEdges(Object)}.
*
* @throws IllegalArgumentException if {@code node} is not an element of this network
*/
Set<E> inEdges(N node);
/**
* Returns all edges in this network which can be traversed in the direction (if any) of the edge
* starting from {@code node}.
*
* <p>In a directed network, an outgoing edge's {@link EndpointPair#source()} equals {@code node}.
*
* <p>In an undirected network, this is equivalent to {@link #incidentEdges(Object)}.
*
* @throws IllegalArgumentException if {@code node} is not an element of this network
*/
Set<E> outEdges(N node);
/**
* Returns the count of {@code node}'s {@link #incidentEdges(Object) incident edges}, counting
* self-loops twice (equivalently, the number of times an edge touches {@code node}).
*
* <p>For directed networks, this is equal to {@code inDegree(node) + outDegree(node)}.
*
* <p>For undirected networks, this is equal to {@code incidentEdges(node).size()} + (number of
* self-loops incident to {@code node}).
*
* <p>If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}.
*
* @throws IllegalArgumentException if {@code node} is not an element of this network
*/
int degree(N node);
/**
* Returns the count of {@code node}'s {@link #inEdges(Object) incoming edges} in a directed
* network. In an undirected network, returns the {@link #degree(Object)}.
*
* <p>If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}.
*
* @throws IllegalArgumentException if {@code node} is not an element of this network
*/
int inDegree(N node);
/**
* Returns the count of {@code node}'s {@link #outEdges(Object) outgoing edges} in a directed
* network. In an undirected network, returns the {@link #degree(Object)}.
*
* <p>If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}.
*
* @throws IllegalArgumentException if {@code node} is not an element of this network
*/
int outDegree(N node);
/**
* Returns the nodes which are the endpoints of {@code edge} in this network.
*
* @throws IllegalArgumentException if {@code edge} is not an element of this network
*/
EndpointPair<N> incidentNodes(E edge);
/**
* Returns the edges which have an {@link #incidentNodes(Object) incident node} in common with
* {@code edge}. An edge is not considered adjacent to itself.
*
* @throws IllegalArgumentException if {@code edge} is not an element of this network
*/
Set<E> adjacentEdges(E edge);
/**
* Returns the set of edges that each directly connect {@code nodeU} to {@code nodeV}.
*
* <p>In an undirected network, this is equal to {@code edgesConnecting(nodeV, nodeU)}.
*
* <p>The resulting set of edges will be parallel (i.e. have equal {@link #incidentNodes(Object)}.
* If this network does not {@link #allowsParallelEdges() allow parallel edges}, the resulting set
* will contain at most one edge (equivalent to {@code edgeConnecting(nodeU, nodeV).asSet()}).
*
* @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this
* network
*/
Set<E> edgesConnecting(N nodeU, N nodeV);
/**
* Returns the set of edges that each directly connect {@code endpoints} (in the order, if any,
* specified by {@code endpoints}).
*
* <p>The resulting set of edges will be parallel (i.e. have equal {@link #incidentNodes(Object)}.
* If this network does not {@link #allowsParallelEdges() allow parallel edges}, the resulting set
* will contain at most one edge (equivalent to {@code edgeConnecting(endpoints).asSet()}).
*
* <p>If this network is directed, {@code endpoints} must be ordered.
*
* @throws IllegalArgumentException if either endpoint is not an element of this network
* @throws IllegalArgumentException if the endpoints are unordered and the graph is directed
*/
Set<E> edgesConnecting(EndpointPair<N> endpoints);
/**
* Returns the single edge that directly connects {@code nodeU} to {@code nodeV}, if one is
* present, or {@code Optional.empty()} if no such edge exists.
*
* <p>In an undirected network, this is equal to {@code edgeConnecting(nodeV, nodeU)}.
*
* @throws IllegalArgumentException if there are multiple parallel edges connecting {@code nodeU}
* to {@code nodeV}
* @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this
* network
*/
Optional<E> edgeConnecting(N nodeU, N nodeV);
/**
* Returns the single edge that directly connects {@code endpoints} (in the order, if any,
* specified by {@code endpoints}), if one is present, or {@code Optional.empty()} if no such edge
* exists.
*
* <p>If this graph is directed, the endpoints must be ordered.
*
* @throws IllegalArgumentException if there are multiple parallel edges connecting {@code nodeU}
* to {@code nodeV}
* @throws IllegalArgumentException if either endpoint is not an element of this network
* @throws IllegalArgumentException if the endpoints are unordered and the graph is directed
*/
Optional<E> edgeConnecting(EndpointPair<N> endpoints);
/**
* Returns the single edge that directly connects {@code nodeU} to {@code nodeV}, if one is
* present, or {@code null} if no such edge exists.
*
* <p>In an undirected network, this is equal to {@code edgeConnectingOrNull(nodeV, nodeU)}.
*
* @throws IllegalArgumentException if there are multiple parallel edges connecting {@code nodeU}
* to {@code nodeV}
* @throws IllegalArgumentException if {@code nodeU} or {@code nodeV} is not an element of this
* network
*/
@Nullable
E edgeConnectingOrNull(N nodeU, N nodeV);
/**
* Returns the single edge that directly connects {@code endpoints} (in the order, if any,
* specified by {@code endpoints}), if one is present, or {@code null} if no such edge exists.
*
* <p>If this graph is directed, the endpoints must be ordered.
*
* @throws IllegalArgumentException if there are multiple parallel edges connecting {@code nodeU}
* to {@code nodeV}
* @throws IllegalArgumentException if either endpoint is not an element of this network
* @throws IllegalArgumentException if the endpoints are unordered and the graph is directed
*/
@Nullable
E edgeConnectingOrNull(EndpointPair<N> endpoints);
/**
* Returns true if there is an edge that directly connects {@code nodeU} to {@code nodeV}. This is
* equivalent to {@code nodes().contains(nodeU) && successors(nodeU).contains(nodeV)}, and to
* {@code edgeConnectingOrNull(nodeU, nodeV) != null}.
*
* <p>In an undirected graph, this is equal to {@code hasEdgeConnecting(nodeV, nodeU)}.
*/
boolean hasEdgeConnecting(N nodeU, N nodeV);
/**
* Returns true if there is an edge that directly connects {@code endpoints} (in the order, if
* any, specified by {@code endpoints}).
*
* <p>Unlike the other {@code EndpointPair}-accepting methods, this method does not throw if the
* endpoints are unordered and the graph is directed; it simply returns {@code false}. This is for
* consistency with {@code com.google.common.graph.Graph#hasEdgeConnecting(EndpointPair)} and {@code
* com.google.common.graph.ValueGraph#hasEdgeConnecting(EndpointPair)}.
*/
boolean hasEdgeConnecting(EndpointPair<N> endpoints);
//endregion
/* ------------------------------------------------------------------------------------------- */
/* ------------------------------------------------------------------------------------------- */
//region Network identity
/**
* Returns {@code true} iff {@code object} is a {@link Network} that has the same elements and the
* same structural relationships as those in this network.
*
* <p>Thus, two networks A and B are equal if <b>all</b> of the following are true:
*
* <ul>
* <li>A and B have equal {@link #isDirected() directedness}.
* <li>A and B have equal {@link #nodes() node sets}.
* <li>A and B have equal {@link #edges() edge sets}.
* <li>Every edge in A and B connects the same nodes in the same direction (if any).
* </ul>
*
* <p>Network properties besides {@link #isDirected() directedness} do <b>not</b> affect equality.
* For example, two networks may be considered equal even if one allows parallel edges and the
* other doesn't. Additionally, the order in which nodes or edges are added to the network, and
* the order in which they are iterated over, are irrelevant.
*/
@Override
boolean equals(@Nullable Object object);
/**
* Returns the hash code for this network. The hash code of a network is defined as the hash code
* of a map from each of its {@link #edges() edges} to their {@link #incidentNodes(Object)
* incident nodes}.
*/
@Override
int hashCode();
//endregion
/* ------------------------------------------------------------------------------------------- */
}
@@ -0,0 +1,156 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* 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.intellij.util.graph;
import java.util.Objects;
import java.util.OptionalInt;
/**
* A builder for constructing instances of {@link MutableNetwork} with
* user-defined properties.
*
* <p>A network built by this class will have the following properties by default:
*
* <ul>
* <li>does not allow parallel edges
* <li>does not allow self-loops
* <li>orders {@link Network#nodes()} and {@link Network#edges()} in the order in which the
* elements were added
* </ul>
*
* <p>Examples of use:
*
* <pre>{@code
* // Building a mutable network
* MutableNetwork<String, Integer> network =
* NetworkBuilder.directed().allowsParallelEdges(true).build();
* flightNetwork.addEdge("LAX", "ATL", 3025);
* flightNetwork.addEdge("LAX", "ATL", 1598);
* flightNetwork.addEdge("ATL", "LAX", 2450);
*
* // Building a immutable network
* ImmutableNetwork<String, Integer> immutableNetwork =
* NetworkBuilder.directed()
* .allowsParallelEdges(true)
* .<String, Integer>immutable()
* .addEdge("LAX", "ATL", 3025)
* .addEdge("LAX", "ATL", 1598)
* .addEdge("ATL", "LAX", 2450)
* .build();
* }</pre>
*
* @author James Sexton
* @author Joshua O'Madadhain
* @param <N> The most general node type this builder will support. This is normally {@code Object}
* unless it is constrained by using a method like {@link #myNodeOrder}, or the builder is
* constructed based on an existing {@code Network}.
* @param <E> The most general edge type this builder will support. This is normally {@code Object}
* unless it is constrained by using a method like {@link #myEdgeOrder}, or the builder is
* constructed based on an existing {@code Network}.
*/
public abstract class NetworkBuilder<N, E> {
protected final boolean myIsDirected;
protected boolean myDoAllowParallelEdges = false;
protected boolean myDoAllowSelfLoops = false;
protected ElementOrder<N> myNodeOrder = ElementOrder.insertion();
protected ElementOrder<? super E> myEdgeOrder = ElementOrder.insertion();
protected ElementOrder<N> myIncidentEdgeOrder = ElementOrder.unordered();
protected OptionalInt myExpectedNodeCount = OptionalInt.empty();
protected OptionalInt myExpectedEdgeCount = OptionalInt.empty();
/** Creates a new instance with the specified edge directionality. */
protected NetworkBuilder(boolean directed) {
myIsDirected = directed;
}
/**
* Specifies whether the network will allow parallel edges. Attempting to add a parallel edge to a
* network that does not allow them will throw an {@link UnsupportedOperationException}.
*
* <p>The default value is {@code false}.
*/
public NetworkBuilder<N, E> allowsParallelEdges(boolean allowsParallelEdges) {
myDoAllowParallelEdges = allowsParallelEdges;
return this;
}
/**
* Specifies whether the network will allow self-loops (edges that connect a node to itself).
* Attempting to add a self-loop to a network that does not allow them will throw an {@link
* UnsupportedOperationException}.
*
* <p>The default value is {@code false}.
*/
public NetworkBuilder<N, E> allowsSelfLoops(boolean allowsSelfLoops) {
myDoAllowSelfLoops = allowsSelfLoops;
return this;
}
/**
* Specifies the expected number of nodes in the network.
*
* @throws IllegalArgumentException if {@code expectedNodeCount} is negative
*/
public NetworkBuilder<N, E> expectedNodeCount(int expectedNodeCount) {
assert expectedNodeCount >= 0;
myExpectedNodeCount = OptionalInt.of(expectedNodeCount);
return this;
}
/**
* Specifies the expected number of edges in the network.
*
* @throws IllegalArgumentException if {@code expectedEdgeCount} is negative
*/
public NetworkBuilder<N, E> expectedEdgeCount(int expectedEdgeCount) {
assert expectedEdgeCount >= 0;
myExpectedEdgeCount = OptionalInt.of(expectedEdgeCount);
return this;
}
/**
* Specifies the order of iteration for the elements of {@link Network#nodes()}.
*
* <p>The default value is {@link ElementOrder#insertion() insertion order}.
*/
public <N1 extends N> NetworkBuilder<N1, E> nodeOrder(ElementOrder<N1> nodeOrder) {
NetworkBuilder<N1, E> newBuilder = cast();
newBuilder.myNodeOrder = Objects.requireNonNull(nodeOrder);
return newBuilder;
}
/**
* Specifies the order of iteration for the elements of {@link Network#edges()}.
*
* <p>The default value is {@link ElementOrder#insertion() insertion order}.
*/
public <E1 extends E> NetworkBuilder<N, E1> edgeOrder(ElementOrder<E1> edgeOrder) {
NetworkBuilder<N, E1> newBuilder = cast();
newBuilder.myEdgeOrder = Objects.requireNonNull(edgeOrder);
return newBuilder;
}
/** Returns an empty {@link MutableNetwork} with the properties of this {@link NetworkBuilder}. */
public abstract <N1 extends N, E1 extends E> MutableNetwork<N1, E1> build();
@SuppressWarnings("unchecked")
private <N1 extends N, E1 extends E> NetworkBuilder<N1, E1> cast() {
return (NetworkBuilder<N1, E1>) this;
}
}