diff --git a/platform/core-api/src/com/intellij/util/graph/GraphFactory.java b/platform/core-api/src/com/intellij/util/graph/GraphFactory.java new file mode 100644 index 000000000000..5492e4464754 --- /dev/null +++ b/platform/core-api/src/com/intellij/util/graph/GraphFactory.java @@ -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 directedNetwork(); + + /** Returns a {@link NetworkBuilder} for building undirected networks. */ + public abstract @NotNull NetworkBuilder undirectedNetwork(); + + /** + * Returns a {@link NetworkBuilder} initialized with all properties queryable from {@code + * network}. + * + *

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 @NotNull NetworkBuilder from(@NotNull Network network); +} diff --git a/platform/core-impl/intellij.platform.core.impl.iml b/platform/core-impl/intellij.platform.core.impl.iml index b2adade661ae..3cea6497cd14 100644 --- a/platform/core-impl/intellij.platform.core.impl.iml +++ b/platform/core-impl/intellij.platform.core.impl.iml @@ -16,5 +16,6 @@ + \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/util/graph/impl/GraphAdapter.java b/platform/core-impl/src/com/intellij/util/graph/impl/GraphAdapter.java new file mode 100644 index 000000000000..e77c43b66179 --- /dev/null +++ b/platform/core-impl/src/com/intellij/util/graph/impl/GraphAdapter.java @@ -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 EndpointPair wrapEndpointsPair(com.google.common.graph.EndpointPair endpoints) { + if (endpoints.isOrdered()) { + return EndpointPair.ordered(endpoints.source(), endpoints.target()); + } + else { + return EndpointPair.unordered(endpoints.nodeU(), endpoints.nodeV()); + } + } + + public static com.google.common.graph.EndpointPair unwrapEndpointsPair(EndpointPair 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 ElementOrder wrapOrder(com.google.common.graph.ElementOrder 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 com.google.common.graph.ElementOrder unwrapOrder(ElementOrder 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 MutableNetwork wrapNetwork(com.google.common.graph.MutableNetwork delegate) { + return new MutableNetwork() { + @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 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 nodes() { + return delegate.nodes(); + } + + @Override + public Set edges() { + return delegate.edges(); + } + + @Override + public Graph asGraph() { + return new Graph() { + @Override + public @NotNull Collection getNodes() { + return nodes(); + } + + @Override + public @NotNull Iterator getIn(N n) { + return predecessors(n).iterator(); + } + + @Override + public @NotNull Iterator 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 nodeOrder() { + return wrapOrder(delegate.nodeOrder()); + } + + @Override + public ElementOrder edgeOrder() { + return wrapOrder(delegate.edgeOrder()); + } + + @Override + public Set adjacentNodes(N n) { + return delegate.adjacentNodes(n); + } + + @Override + public Set predecessors(N n) { + return delegate.predecessors(n); + } + + @Override + public Set successors(N n) { + return delegate.successors(n); + } + + @Override + public Set incidentEdges(N n) { + return delegate.incidentEdges(n); + } + + @Override + public Set inEdges(N n) { + return delegate.inEdges(n); + } + + @Override + public Set 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 incidentNodes(E e) { + return wrapEndpointsPair(delegate.incidentNodes(e)); + } + + @Override + public Set adjacentEdges(E e) { + return delegate.adjacentEdges(e); + } + + @Override + public Set edgesConnecting(N n, N n1) { + return delegate.edgesConnecting(n, n1); + } + + @Override + public Optional 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 edgesConnecting(EndpointPair endpoints) { + return delegate.edgesConnecting(unwrapEndpointsPair(endpoints)); + } + + @Override + public Optional edgeConnecting(EndpointPair endpoints) { + return delegate.edgeConnecting(unwrapEndpointsPair(endpoints)); + } + + @Override + public @Nullable E edgeConnectingOrNull(EndpointPair endpoints) { + return delegate.edgeConnectingOrNull(unwrapEndpointsPair(endpoints)); + } + + @Override + public boolean hasEdgeConnecting(N n, N n1) { + return delegate.hasEdgeConnecting(n, n1); + } + + @Override + public boolean hasEdgeConnecting(EndpointPair 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(); + } + }; + } +} diff --git a/platform/core-impl/src/com/intellij/util/graph/impl/GraphFactoryImpl.java b/platform/core-impl/src/com/intellij/util/graph/impl/GraphFactoryImpl.java new file mode 100644 index 000000000000..ba5b72a060e8 --- /dev/null +++ b/platform/core-impl/src/com/intellij/util/graph/impl/GraphFactoryImpl.java @@ -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 directedNetwork() { + return new NetworkBuilderImpl<>(true); + } + + @Override + public @NotNull NetworkBuilder undirectedNetwork() { + return new NetworkBuilderImpl<>(false); + } + + @Override + public @NotNull NetworkBuilder from(@NotNull Network network) { + return new NetworkBuilderImpl(network.isDirected()) + .allowsParallelEdges(network.allowsParallelEdges()) + .allowsSelfLoops(network.allowsSelfLoops()) + .nodeOrder(network.nodeOrder()) + .edgeOrder(network.edgeOrder()); + } +} diff --git a/platform/core-impl/src/com/intellij/util/graph/impl/NetworkBuilderImpl.java b/platform/core-impl/src/com/intellij/util/graph/impl/NetworkBuilderImpl.java new file mode 100644 index 000000000000..82e802c2e559 --- /dev/null +++ b/platform/core-impl/src/com/intellij/util/graph/impl/NetworkBuilderImpl.java @@ -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 extends NetworkBuilder { + + protected NetworkBuilderImpl(boolean directed) { + super(directed); + } + + @Override + public MutableNetwork build() { + com.google.common.graph.NetworkBuilder 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()); + } +} diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index b7a78f3f2ba3..538bb59f36fc 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -339,6 +339,8 @@ + diff --git a/platform/util/src/com/intellij/util/graph/ElementOrder.java b/platform/util/src/com/intellij/util/graph/ElementOrder.java new file mode 100644 index 000000000000..2b329e46d8ae --- /dev/null +++ b/platform/util/src/com/intellij/util/graph/ElementOrder.java @@ -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.*; + + +/** + *

NOTE: 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.

+ * + *

The description from Guava sources:

+ *

+ * 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 { + private final Type myType; + + @SuppressWarnings("Immutable") // Hopefully the comparator provided is immutable! + private final @Nullable Comparator myComparator; + + /** + * The type of ordering that this object specifies. + * + *

+ */ + public enum Type { + UNORDERED, + STABLE, + INSERTION, + SORTED + } + + private ElementOrder(Type type, @Nullable Comparator 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 ElementOrder 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. + * + *

This instance is only useful in combination with {@code incidentEdgeOrder}, e.g. {@code + * graphBuilder.incidentEdgeOrder(ElementOrder.stable())}. + * + *

In combination with {@code incidentEdgeOrder}

+ * + *

{@code incidentEdgeOrder(ElementOrder.stable())} guarantees the ordering of the returned + * collections of the following methods: + * + *

    + *
  • For {@link Graph}: + *
      + *
    • {@code edges()}: Stable order + *
    • {@code adjacentNodes(node)}: Connecting edge insertion order + *
    • {@code predecessors(node)}: Connecting edge insertion order + *
    • {@code successors(node)}: Connecting edge insertion order + *
    • {@code incidentEdges(node)}: Edge insertion order + *
    + *
  • For {@link Network}: + *
      + *
    • {@code adjacentNodes(node)}: Stable order + *
    • {@code predecessors(node)}: Connecting edge insertion order + *
    • {@code successors(node)}: Connecting edge insertion order + *
    • {@code incidentEdges(node)}: Stable order + *
    • {@code inEdges(node)}: Edge insertion order + *
    • {@code outEdges(node)}: Edge insertion order + *
    • {@code adjacentEdges(edge)}: Stable order + *
    • {@code edgesConnecting(nodeU, nodeV)}: Edge insertion order + *
    + *
+ */ + public static ElementOrder stable() { + return new ElementOrder<>(Type.STABLE, null); + } + + /** + * Returns an instance which specifies that insertion ordering is guaranteed. + */ + public static ElementOrder insertion() { + return new ElementOrder<>(Type.INSERTION, null); + } + + /** + * Returns an instance which specifies that the natural ordering of the elements is guaranteed. + */ + public static > ElementOrder natural() { + return new ElementOrder<>(Type.SORTED, Comparator.naturalOrder()); + } + + /** + * Returns an instance which specifies that the ordering of the elements is guaranteed to be + * determined by {@code comparator}. + */ + public static ElementOrder sorted(Comparator comparator) { + return new ElementOrder(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 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 + '}'; + } +} diff --git a/platform/util/src/com/intellij/util/graph/EndpointPair.java b/platform/util/src/com/intellij/util/graph/EndpointPair.java new file mode 100644 index 000000000000..edf74372ab34 --- /dev/null +++ b/platform/util/src/com/intellij/util/graph/EndpointPair.java @@ -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; + + +/** + * NOTE: 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. + * + *

The description from Guava sources: + * + *

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()}). + * + *

The edge is a self-loop if, and only if, the two endpoints are equal. + * + * @author James Sexton + */ +public abstract class EndpointPair implements Iterable { + 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 EndpointPair ordered(N source, N target) { + return new Ordered<>(source, target); + } + + /** + * Returns an {@link EndpointPair} representing the endpoints of an undirected edge. + */ + public static EndpointPair 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 EndpointPair 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 EndpointPair 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 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 extends EndpointPair { + 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 extends EndpointPair { + 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() + "]"; + } + } +} diff --git a/platform/util/src/com/intellij/util/graph/MutableNetwork.java b/platform/util/src/com/intellij/util/graph/MutableNetwork.java new file mode 100644 index 000000000000..9ffb705aaf84 --- /dev/null +++ b/platform/util/src/com/intellij/util/graph/MutableNetwork.java @@ -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; + + +/** + *

NOTE: 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.

+ * + *

The description from Guava sources:

+ * + * 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 Node parameter type + * @param Edge parameter type + */ +public interface MutableNetwork extends Network { + + /** + * Adds {@code node} if it is not already present. + * + *

Nodes must be unique, 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}. + * + *

If the graph is directed, {@code edge} will be directed in this graph; otherwise, it will be + * undirected. + * + *

{@code edge} must be unique to this graph, just as a {@code Map} key must be. It must + * also be non-null. + * + *

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. + * + *

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}. + * + *

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. + * + *

If this graph is directed, {@code endpoints} must be ordered. + * + *

{@code edge} must be unique to this graph, just as a {@code Map} key must be. It must + * also be non-null. + * + *

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. + * + *

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 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); +} diff --git a/platform/util/src/com/intellij/util/graph/Network.java b/platform/util/src/com/intellij/util/graph/Network.java new file mode 100644 index 000000000000..3c0e8b525646 --- /dev/null +++ b/platform/util/src/com/intellij/util/graph/Network.java @@ -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; + + +/** + *

NOTE: 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.

+ * + *

The description from Guava sources:

+ *

+ * An interface for graph-structured data, + * whose edges are unique objects. + * + *

A graph is composed of a set of nodes and a set of edges connecting pairs of nodes. + * + *

Capabilities

+ * + *

{@code Network} supports the following use cases (definitions of + * terms): + * + *

    + *
  • directed graphs + *
  • undirected graphs + *
  • graphs that do/don't allow parallel edges + *
  • graphs that do/don't allow self-loops + *
  • graphs whose nodes/edges are insertion-ordered, sorted, or unordered + *
  • graphs whose edges are unique objects + *
+ * + *

Building a {@code Network}

+ * + *

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: + * + *

{@code
+ * final var graph = NetworkBuilder.directed().build();
+ * }
+ * + *

The Guava User Guide has more + * information on (and examples of) building graphs. + * + *

Additional documentation

+ * + *

See the Guava User Guide for the {@code common.graph} package ("Graphs Explained") for + * additional documentation, including: + * + *

+ * + * @author James Sexton + * @author Joshua O'Madadhain + * @param Node parameter type + * @param Edge parameter type + */ +public interface Network { + + /* ------------------------------------------------------------------------------------------- */ + //region Network-level accessors + + /** + * Returns all nodes in this network, in the order specified by {@link #nodeOrder()}. + */ + Set nodes(); + + /** + * Returns all edges in this network, in the order specified by {@link #edgeOrder()}. + */ + Set 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. + * + *

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 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 nodeOrder(); + + /** + * Returns the order of iteration for the elements of {@link #edges()}. + */ + ElementOrder edgeOrder(); + + //endregion + /* ------------------------------------------------------------------------------------------- */ + + + /* ------------------------------------------------------------------------------------------- */ + //region Element-level accessors + + /** + * Returns the nodes which have an incident edge in common with {@code node} in this network. + * + *

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 adjacentNodes(N node); + + /** + * Returns all nodes in this network adjacent to {@code node} which can be reached by traversing + * {@code node}'s incoming edges against the direction (if any) of the edge. + * + *

In an undirected network, this is equivalent to {@link #adjacentNodes(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + Set 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. + * + *

In an undirected network, this is equivalent to {@link #adjacentNodes(Object)}. + * + *

This is not 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 successors(N node); + + /** + * Returns the edges whose {@link #incidentNodes(Object) incident nodes} in this network include + * {@code node}. + * + *

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 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}. + * + *

In a directed network, an incoming edge's {@link EndpointPair#target()} equals {@code node}. + * + *

In an undirected network, this is equivalent to {@link #incidentEdges(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + Set 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}. + * + *

In a directed network, an outgoing edge's {@link EndpointPair#source()} equals {@code node}. + * + *

In an undirected network, this is equivalent to {@link #incidentEdges(Object)}. + * + * @throws IllegalArgumentException if {@code node} is not an element of this network + */ + Set 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}). + * + *

For directed networks, this is equal to {@code inDegree(node) + outDegree(node)}. + * + *

For undirected networks, this is equal to {@code incidentEdges(node).size()} + (number of + * self-loops incident to {@code node}). + * + *

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)}. + * + *

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)}. + * + *

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 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 adjacentEdges(E edge); + + /** + * Returns the set of edges that each directly connect {@code nodeU} to {@code nodeV}. + * + *

In an undirected network, this is equal to {@code edgesConnecting(nodeV, nodeU)}. + * + *

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 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}). + * + *

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()}). + * + *

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 edgesConnecting(EndpointPair 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. + * + *

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 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. + * + *

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 edgeConnecting(EndpointPair 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. + * + *

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. + * + *

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 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}. + * + *

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}). + * + *

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 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. + * + *

Thus, two networks A and B are equal if all of the following are true: + * + *

    + *
  • A and B have equal {@link #isDirected() directedness}. + *
  • A and B have equal {@link #nodes() node sets}. + *
  • A and B have equal {@link #edges() edge sets}. + *
  • Every edge in A and B connects the same nodes in the same direction (if any). + *
+ * + *

Network properties besides {@link #isDirected() directedness} do not 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 + /* ------------------------------------------------------------------------------------------- */ +} diff --git a/platform/util/src/com/intellij/util/graph/NetworkBuilder.java b/platform/util/src/com/intellij/util/graph/NetworkBuilder.java new file mode 100644 index 000000000000..e0d7c7f3dc03 --- /dev/null +++ b/platform/util/src/com/intellij/util/graph/NetworkBuilder.java @@ -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. + * + *

A network built by this class will have the following properties by default: + * + *

    + *
  • does not allow parallel edges + *
  • does not allow self-loops + *
  • orders {@link Network#nodes()} and {@link Network#edges()} in the order in which the + * elements were added + *
+ * + *

Examples of use: + * + *

{@code
+ * // Building a mutable network
+ * MutableNetwork 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 immutableNetwork =
+ *     NetworkBuilder.directed()
+ *         .allowsParallelEdges(true)
+ *         .immutable()
+ *         .addEdge("LAX", "ATL", 3025)
+ *         .addEdge("LAX", "ATL", 1598)
+ *         .addEdge("ATL", "LAX", 2450)
+ *         .build();
+ * }
+ * + * @author James Sexton + * @author Joshua O'Madadhain + * @param 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 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 { + protected final boolean myIsDirected; + protected boolean myDoAllowParallelEdges = false; + protected boolean myDoAllowSelfLoops = false; + + protected ElementOrder myNodeOrder = ElementOrder.insertion(); + protected ElementOrder myEdgeOrder = ElementOrder.insertion(); + protected ElementOrder 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}. + * + *

The default value is {@code false}. + */ + public NetworkBuilder 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}. + * + *

The default value is {@code false}. + */ + public NetworkBuilder 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 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 expectedEdgeCount(int expectedEdgeCount) { + assert expectedEdgeCount >= 0; + myExpectedEdgeCount = OptionalInt.of(expectedEdgeCount); + return this; + } + + /** + * Specifies the order of iteration for the elements of {@link Network#nodes()}. + * + *

The default value is {@link ElementOrder#insertion() insertion order}. + */ + public NetworkBuilder nodeOrder(ElementOrder nodeOrder) { + NetworkBuilder newBuilder = cast(); + newBuilder.myNodeOrder = Objects.requireNonNull(nodeOrder); + return newBuilder; + } + + /** + * Specifies the order of iteration for the elements of {@link Network#edges()}. + * + *

The default value is {@link ElementOrder#insertion() insertion order}. + */ + public NetworkBuilder edgeOrder(ElementOrder edgeOrder) { + NetworkBuilder newBuilder = cast(); + newBuilder.myEdgeOrder = Objects.requireNonNull(edgeOrder); + return newBuilder; + } + + /** Returns an empty {@link MutableNetwork} with the properties of this {@link NetworkBuilder}. */ + public abstract MutableNetwork build(); + + @SuppressWarnings("unchecked") + private NetworkBuilder cast() { + return (NetworkBuilder) this; + } +}