mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[graphchart] New API for graph diagrams providing
GitOrigin-RevId: 97b4a5adfd76c0b598a768fa6d2b78d8095834f7
This commit is contained in:
committed by
intellij-monorepo-bot
parent
48a6e67125
commit
3d81756b29
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user