diff --git a/android/guava-tests/test/com/google/common/graph/ImmutableGraphTest.java b/android/guava-tests/test/com/google/common/graph/ImmutableGraphTest.java new file mode 100644 index 000000000000..1c1eddde6c73 --- /dev/null +++ b/android/guava-tests/test/com/google/common/graph/ImmutableGraphTest.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import org.jspecify.annotations.NullUnmarked; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link ImmutableGraph}. */ +@RunWith(JUnit4.class) +@NullUnmarked +public class ImmutableGraphTest { + + @Test + public void immutableGraph() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + ImmutableGraph immutableGraph = ImmutableGraph.copyOf(mutableGraph); + + assertThat(immutableGraph).isNotInstanceOf(MutableGraph.class); + assertThat(immutableGraph).isEqualTo(mutableGraph); + + mutableGraph.addNode("B"); + assertThat(immutableGraph).isNotEqualTo(mutableGraph); + } + + @Test + public void copyOfImmutableGraph_optimized() { + Graph graph1 = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + Graph graph2 = ImmutableGraph.copyOf(graph1); + + assertThat(graph2).isSameInstanceAs(graph1); + } + + @Test + public void incidentEdgeOrder_stable() { + ImmutableGraph immutableGraph = + ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThat(immutableGraph.incidentEdgeOrder()).isEqualTo(ElementOrder.stable()); + } + + @Test + public void emptyGraph_nodes_unmodifiable() { + ImmutableGraph graph = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().add("A")); + } + + @Test + public void emptyGraph_nodes_clear() { + ImmutableGraph graph = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().clear()); + } + + @Test + public void emptyGraph_edges_unmodifiable() { + ImmutableGraph graph = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().add(EndpointPair.ordered("A", "B"))); + } + + @Test + public void emptyGraph_edges_clear() { + ImmutableGraph graph = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThrows(UnsupportedOperationException.class, () -> graph.edges().clear()); + } + + @Test + public void nonEmptyGraph_nodes_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + mutableGraph.addNode("B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().remove("A")); + } + + @Test + public void nonEmptyGraph_nodes_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().clear()); + } + + @Test + public void nonEmptyGraph_nodes_removeAll() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + mutableGraph.addNode("B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.nodes().removeAll(java.util.Arrays.asList("A"))); + } + + @Test + public void nonEmptyGraph_edges_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().add(EndpointPair.ordered("B", "C"))); + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().remove(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_edges_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.edges().clear()); + } + + @Test + public void nonEmptyGraph_adjacentNodes_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").remove("B")); + } + + @Test + public void nonEmptyGraph_adjacentNodes_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").clear()); + } + + @Test + public void nonEmptyGraph_predecessors_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").remove("A")); + } + + @Test + public void nonEmptyGraph_predecessors_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").clear()); + } + + @Test + public void nonEmptyGraph_successors_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").remove("B")); + } + + @Test + public void nonEmptyGraph_successors_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").clear()); + } + + @Test + public void nonEmptyGraph_incidentEdges_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.incidentEdges("A").add(EndpointPair.ordered("A", "C"))); + assertThrows( + UnsupportedOperationException.class, + () -> graph.incidentEdges("A").remove(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_incidentEdges_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.incidentEdges("A").clear()); + } + + @Test + public void nonEmptyGraph_nodes_iteratorRemove() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + java.util.Iterator iterator = graph.nodes().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } + + @Test + public void nonEmptyGraph_edges_iteratorRemove() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + java.util.Iterator> iterator = graph.edges().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } +} diff --git a/android/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java b/android/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java index 646424e1c341..dbe20136b84c 100644 --- a/android/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java +++ b/android/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java @@ -17,6 +17,7 @@ package com.google.common.graph; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import org.jspecify.annotations.NullUnmarked; import org.junit.Test; @@ -145,4 +146,110 @@ public void immutableNetworkBuilder_putEdgeFromEndpointPair() { assertThat(network.edges()).containsExactly(10); assertThat(network.incidentNodes(10)).isEqualTo(EndpointPair.ordered("A", "B")); } + + @Test + public void emptyNetwork_nodes_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().build(); + + assertThrows(UnsupportedOperationException.class, () -> network.nodes().add("A")); + } + + @Test + public void emptyNetwork_edges_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().build(); + + assertThrows(UnsupportedOperationException.class, () -> network.edges().add(10)); + } + + @Test + public void nonEmptyNetwork_nodes_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addNode("A").addNode("B").build(); + + assertThrows(UnsupportedOperationException.class, () -> network.nodes().add("C")); + assertThrows(UnsupportedOperationException.class, () -> network.nodes().remove("A")); + } + + @Test + public void nonEmptyNetwork_edges_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.edges().add(20)); + assertThrows(UnsupportedOperationException.class, () -> network.edges().remove(10)); + } + + @Test + public void nonEmptyNetwork_adjacentNodes_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.adjacentNodes("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> network.adjacentNodes("A").remove("B")); + } + + @Test + public void nonEmptyNetwork_predecessors_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.predecessors("B").add("C")); + assertThrows(UnsupportedOperationException.class, () -> network.predecessors("B").remove("A")); + } + + @Test + public void nonEmptyNetwork_successors_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.successors("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> network.successors("A").remove("B")); + } + + @Test + public void nonEmptyNetwork_incidentEdges_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.incidentEdges("A").add(20)); + assertThrows(UnsupportedOperationException.class, () -> network.incidentEdges("A").remove(10)); + } + + @Test + public void nonEmptyNetwork_nodes_clear() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addNode("A").build(); + + assertThrows(UnsupportedOperationException.class, () -> network.nodes().clear()); + } + + @Test + public void nonEmptyNetwork_edges_clear() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.edges().clear()); + } + + @Test + public void nonEmptyNetwork_nodes_iteratorRemove() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addNode("A").build(); + + java.util.Iterator iterator = network.nodes().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } + + @Test + public void nonEmptyNetwork_edges_iteratorRemove() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + java.util.Iterator iterator = network.edges().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } } diff --git a/android/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java b/android/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java index 6bec95ca5853..fd4d0e92af3b 100644 --- a/android/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java +++ b/android/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java @@ -17,6 +17,7 @@ package com.google.common.graph; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import org.jspecify.annotations.NullUnmarked; import org.junit.Test; @@ -177,4 +178,151 @@ public void immutableValueGraphBuilder_fromUnorderedBuilder_incidentEdgeOrder_st assertThat(graph.incidentEdgeOrder()).isEqualTo(ElementOrder.stable()); } + + @Test + public void emptyGraph_nodes_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed().immutable().build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().add("A")); + } + + @Test + public void emptyGraph_edges_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed().immutable().build(); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().add(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_nodes_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .addNode("A") + .addNode("B") + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().remove("A")); + } + + @Test + public void nonEmptyGraph_edges_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().add(EndpointPair.ordered("B", "C"))); + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().remove(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_adjacentNodes_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").remove("B")); + } + + @Test + public void nonEmptyGraph_predecessors_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").remove("A")); + } + + @Test + public void nonEmptyGraph_successors_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").remove("B")); + } + + @Test + public void nonEmptyGraph_incidentEdges_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.incidentEdges("A").add(EndpointPair.ordered("A", "C"))); + assertThrows( + UnsupportedOperationException.class, + () -> graph.incidentEdges("A").remove(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_nodes_clear() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .addNode("A") + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().clear()); + } + + @Test + public void nonEmptyGraph_edges_clear() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.edges().clear()); + } + + @Test + public void nonEmptyGraph_nodes_iteratorRemove() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .addNode("A") + .build(); + + java.util.Iterator iterator = graph.nodes().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } + + @Test + public void nonEmptyGraph_edges_iteratorRemove() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + java.util.Iterator> iterator = graph.edges().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } } diff --git a/android/guava/src/com/google/common/graph/ImmutableGraph.java b/android/guava/src/com/google/common/graph/ImmutableGraph.java index d9048f366537..03b3952b7951 100644 --- a/android/guava/src/com/google/common/graph/ImmutableGraph.java +++ b/android/guava/src/com/google/common/graph/ImmutableGraph.java @@ -27,6 +27,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.errorprone.annotations.InlineMe; +import java.util.Collections; +import java.util.Set; /** * A {@link Graph} whose elements and structural relationships will never change. Instances of this @@ -108,6 +110,36 @@ BaseGraph delegate() { return backingGraph; } + @Override + public Set nodes() { + return Collections.unmodifiableSet(super.nodes()); + } + + @Override + public Set> edges() { + return Collections.unmodifiableSet(super.edges()); + } + + @Override + public Set adjacentNodes(N node) { + return Collections.unmodifiableSet(super.adjacentNodes(node)); + } + + @Override + public Set predecessors(N node) { + return Collections.unmodifiableSet(super.predecessors(node)); + } + + @Override + public Set successors(N node) { + return Collections.unmodifiableSet(super.successors(node)); + } + + @Override + public Set> incidentEdges(N node) { + return Collections.unmodifiableSet(super.incidentEdges(node)); + } + /** * A builder for creating {@link ImmutableGraph} instances, especially {@code static final} * graphs. Example: diff --git a/android/guava/src/com/google/common/graph/ImmutableNetwork.java b/android/guava/src/com/google/common/graph/ImmutableNetwork.java index e210be0d5275..6d1dc29336f2 100644 --- a/android/guava/src/com/google/common/graph/ImmutableNetwork.java +++ b/android/guava/src/com/google/common/graph/ImmutableNetwork.java @@ -25,7 +25,9 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.errorprone.annotations.InlineMe; +import java.util.Collections; import java.util.Map; +import java.util.Set; /** * A {@link Network} whose elements and structural relationships will never change. Instances of @@ -79,6 +81,36 @@ public ImmutableGraph asGraph() { return new ImmutableGraph<>(super.asGraph()); // safe because the view is effectively immutable } + @Override + public Set nodes() { + return Collections.unmodifiableSet(super.nodes()); + } + + @Override + public Set edges() { + return Collections.unmodifiableSet(super.edges()); + } + + @Override + public Set adjacentNodes(N node) { + return Collections.unmodifiableSet(super.adjacentNodes(node)); + } + + @Override + public Set predecessors(N node) { + return Collections.unmodifiableSet(super.predecessors(node)); + } + + @Override + public Set successors(N node) { + return Collections.unmodifiableSet(super.successors(node)); + } + + @Override + public Set incidentEdges(N node) { + return Collections.unmodifiableSet(super.incidentEdges(node)); + } + private static Map> getNodeConnections(Network network) { // ImmutableMap.Builder maintains the order of the elements as inserted, so the map will have // whatever ordering the network's nodes do, so ImmutableSortedMap is unnecessary even if the diff --git a/android/guava/src/com/google/common/graph/ImmutableValueGraph.java b/android/guava/src/com/google/common/graph/ImmutableValueGraph.java index 8f09d6f9096e..9ec94daa507e 100644 --- a/android/guava/src/com/google/common/graph/ImmutableValueGraph.java +++ b/android/guava/src/com/google/common/graph/ImmutableValueGraph.java @@ -26,6 +26,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.errorprone.annotations.InlineMe; +import java.util.Collections; +import java.util.Set; /** * A {@link ValueGraph} whose elements and structural relationships will never change. Instances of @@ -81,6 +83,36 @@ public ImmutableGraph asGraph() { return new ImmutableGraph<>(this); // safe because the view is effectively immutable } + @Override + public Set nodes() { + return Collections.unmodifiableSet(super.nodes()); + } + + @Override + public Set> edges() { + return Collections.unmodifiableSet(super.edges()); + } + + @Override + public Set adjacentNodes(N node) { + return Collections.unmodifiableSet(super.adjacentNodes(node)); + } + + @Override + public Set predecessors(N node) { + return Collections.unmodifiableSet(super.predecessors(node)); + } + + @Override + public Set successors(N node) { + return Collections.unmodifiableSet(super.successors(node)); + } + + @Override + public Set> incidentEdges(N node) { + return Collections.unmodifiableSet(super.incidentEdges(node)); + } + private static ImmutableMap> getNodeConnections( ValueGraph graph) { // ImmutableMap.Builder maintains the order of the elements as inserted, so the map will have diff --git a/guava-tests/test/com/google/common/graph/ImmutableGraphTest.java b/guava-tests/test/com/google/common/graph/ImmutableGraphTest.java new file mode 100644 index 000000000000..1c1eddde6c73 --- /dev/null +++ b/guava-tests/test/com/google/common/graph/ImmutableGraphTest.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2014 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import org.jspecify.annotations.NullUnmarked; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link ImmutableGraph}. */ +@RunWith(JUnit4.class) +@NullUnmarked +public class ImmutableGraphTest { + + @Test + public void immutableGraph() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + ImmutableGraph immutableGraph = ImmutableGraph.copyOf(mutableGraph); + + assertThat(immutableGraph).isNotInstanceOf(MutableGraph.class); + assertThat(immutableGraph).isEqualTo(mutableGraph); + + mutableGraph.addNode("B"); + assertThat(immutableGraph).isNotEqualTo(mutableGraph); + } + + @Test + public void copyOfImmutableGraph_optimized() { + Graph graph1 = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + Graph graph2 = ImmutableGraph.copyOf(graph1); + + assertThat(graph2).isSameInstanceAs(graph1); + } + + @Test + public void incidentEdgeOrder_stable() { + ImmutableGraph immutableGraph = + ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThat(immutableGraph.incidentEdgeOrder()).isEqualTo(ElementOrder.stable()); + } + + @Test + public void emptyGraph_nodes_unmodifiable() { + ImmutableGraph graph = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().add("A")); + } + + @Test + public void emptyGraph_nodes_clear() { + ImmutableGraph graph = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().clear()); + } + + @Test + public void emptyGraph_edges_unmodifiable() { + ImmutableGraph graph = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().add(EndpointPair.ordered("A", "B"))); + } + + @Test + public void emptyGraph_edges_clear() { + ImmutableGraph graph = ImmutableGraph.copyOf(GraphBuilder.directed().build()); + + assertThrows(UnsupportedOperationException.class, () -> graph.edges().clear()); + } + + @Test + public void nonEmptyGraph_nodes_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + mutableGraph.addNode("B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().remove("A")); + } + + @Test + public void nonEmptyGraph_nodes_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().clear()); + } + + @Test + public void nonEmptyGraph_nodes_removeAll() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + mutableGraph.addNode("B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.nodes().removeAll(java.util.Arrays.asList("A"))); + } + + @Test + public void nonEmptyGraph_edges_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().add(EndpointPair.ordered("B", "C"))); + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().remove(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_edges_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.edges().clear()); + } + + @Test + public void nonEmptyGraph_adjacentNodes_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").remove("B")); + } + + @Test + public void nonEmptyGraph_adjacentNodes_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").clear()); + } + + @Test + public void nonEmptyGraph_predecessors_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").remove("A")); + } + + @Test + public void nonEmptyGraph_predecessors_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").clear()); + } + + @Test + public void nonEmptyGraph_successors_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").remove("B")); + } + + @Test + public void nonEmptyGraph_successors_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").clear()); + } + + @Test + public void nonEmptyGraph_incidentEdges_unmodifiable() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.incidentEdges("A").add(EndpointPair.ordered("A", "C"))); + assertThrows( + UnsupportedOperationException.class, + () -> graph.incidentEdges("A").remove(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_incidentEdges_clear() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + assertThrows(UnsupportedOperationException.class, () -> graph.incidentEdges("A").clear()); + } + + @Test + public void nonEmptyGraph_nodes_iteratorRemove() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.addNode("A"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + java.util.Iterator iterator = graph.nodes().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } + + @Test + public void nonEmptyGraph_edges_iteratorRemove() { + MutableGraph mutableGraph = GraphBuilder.directed().build(); + mutableGraph.putEdge("A", "B"); + ImmutableGraph graph = ImmutableGraph.copyOf(mutableGraph); + + java.util.Iterator> iterator = graph.edges().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } +} diff --git a/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java b/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java index 646424e1c341..dbe20136b84c 100644 --- a/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java +++ b/guava-tests/test/com/google/common/graph/ImmutableNetworkTest.java @@ -17,6 +17,7 @@ package com.google.common.graph; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import org.jspecify.annotations.NullUnmarked; import org.junit.Test; @@ -145,4 +146,110 @@ public void immutableNetworkBuilder_putEdgeFromEndpointPair() { assertThat(network.edges()).containsExactly(10); assertThat(network.incidentNodes(10)).isEqualTo(EndpointPair.ordered("A", "B")); } + + @Test + public void emptyNetwork_nodes_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().build(); + + assertThrows(UnsupportedOperationException.class, () -> network.nodes().add("A")); + } + + @Test + public void emptyNetwork_edges_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().build(); + + assertThrows(UnsupportedOperationException.class, () -> network.edges().add(10)); + } + + @Test + public void nonEmptyNetwork_nodes_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addNode("A").addNode("B").build(); + + assertThrows(UnsupportedOperationException.class, () -> network.nodes().add("C")); + assertThrows(UnsupportedOperationException.class, () -> network.nodes().remove("A")); + } + + @Test + public void nonEmptyNetwork_edges_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.edges().add(20)); + assertThrows(UnsupportedOperationException.class, () -> network.edges().remove(10)); + } + + @Test + public void nonEmptyNetwork_adjacentNodes_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.adjacentNodes("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> network.adjacentNodes("A").remove("B")); + } + + @Test + public void nonEmptyNetwork_predecessors_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.predecessors("B").add("C")); + assertThrows(UnsupportedOperationException.class, () -> network.predecessors("B").remove("A")); + } + + @Test + public void nonEmptyNetwork_successors_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.successors("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> network.successors("A").remove("B")); + } + + @Test + public void nonEmptyNetwork_incidentEdges_unmodifiable() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.incidentEdges("A").add(20)); + assertThrows(UnsupportedOperationException.class, () -> network.incidentEdges("A").remove(10)); + } + + @Test + public void nonEmptyNetwork_nodes_clear() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addNode("A").build(); + + assertThrows(UnsupportedOperationException.class, () -> network.nodes().clear()); + } + + @Test + public void nonEmptyNetwork_edges_clear() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + assertThrows(UnsupportedOperationException.class, () -> network.edges().clear()); + } + + @Test + public void nonEmptyNetwork_nodes_iteratorRemove() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addNode("A").build(); + + java.util.Iterator iterator = network.nodes().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } + + @Test + public void nonEmptyNetwork_edges_iteratorRemove() { + ImmutableNetwork network = + NetworkBuilder.directed().immutable().addEdge("A", "B", 10).build(); + + java.util.Iterator iterator = network.edges().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } } diff --git a/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java b/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java index 6bec95ca5853..fd4d0e92af3b 100644 --- a/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java +++ b/guava-tests/test/com/google/common/graph/ImmutableValueGraphTest.java @@ -17,6 +17,7 @@ package com.google.common.graph; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import org.jspecify.annotations.NullUnmarked; import org.junit.Test; @@ -177,4 +178,151 @@ public void immutableValueGraphBuilder_fromUnorderedBuilder_incidentEdgeOrder_st assertThat(graph.incidentEdgeOrder()).isEqualTo(ElementOrder.stable()); } + + @Test + public void emptyGraph_nodes_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed().immutable().build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().add("A")); + } + + @Test + public void emptyGraph_edges_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed().immutable().build(); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().add(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_nodes_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .addNode("A") + .addNode("B") + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().remove("A")); + } + + @Test + public void nonEmptyGraph_edges_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().add(EndpointPair.ordered("B", "C"))); + assertThrows( + UnsupportedOperationException.class, + () -> graph.edges().remove(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_adjacentNodes_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.adjacentNodes("A").remove("B")); + } + + @Test + public void nonEmptyGraph_predecessors_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.predecessors("B").remove("A")); + } + + @Test + public void nonEmptyGraph_successors_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").add("C")); + assertThrows(UnsupportedOperationException.class, () -> graph.successors("A").remove("B")); + } + + @Test + public void nonEmptyGraph_incidentEdges_unmodifiable() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows( + UnsupportedOperationException.class, + () -> graph.incidentEdges("A").add(EndpointPair.ordered("A", "C"))); + assertThrows( + UnsupportedOperationException.class, + () -> graph.incidentEdges("A").remove(EndpointPair.ordered("A", "B"))); + } + + @Test + public void nonEmptyGraph_nodes_clear() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .addNode("A") + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.nodes().clear()); + } + + @Test + public void nonEmptyGraph_edges_clear() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + assertThrows(UnsupportedOperationException.class, () -> graph.edges().clear()); + } + + @Test + public void nonEmptyGraph_nodes_iteratorRemove() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .addNode("A") + .build(); + + java.util.Iterator iterator = graph.nodes().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } + + @Test + public void nonEmptyGraph_edges_iteratorRemove() { + ImmutableValueGraph graph = + ValueGraphBuilder.directed() + .immutable() + .putEdgeValue("A", "B", 10) + .build(); + + java.util.Iterator> iterator = graph.edges().iterator(); + iterator.next(); + assertThrows(UnsupportedOperationException.class, () -> iterator.remove()); + } } diff --git a/guava/src/com/google/common/graph/ImmutableGraph.java b/guava/src/com/google/common/graph/ImmutableGraph.java index d9048f366537..03b3952b7951 100644 --- a/guava/src/com/google/common/graph/ImmutableGraph.java +++ b/guava/src/com/google/common/graph/ImmutableGraph.java @@ -27,6 +27,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.errorprone.annotations.InlineMe; +import java.util.Collections; +import java.util.Set; /** * A {@link Graph} whose elements and structural relationships will never change. Instances of this @@ -108,6 +110,36 @@ BaseGraph delegate() { return backingGraph; } + @Override + public Set nodes() { + return Collections.unmodifiableSet(super.nodes()); + } + + @Override + public Set> edges() { + return Collections.unmodifiableSet(super.edges()); + } + + @Override + public Set adjacentNodes(N node) { + return Collections.unmodifiableSet(super.adjacentNodes(node)); + } + + @Override + public Set predecessors(N node) { + return Collections.unmodifiableSet(super.predecessors(node)); + } + + @Override + public Set successors(N node) { + return Collections.unmodifiableSet(super.successors(node)); + } + + @Override + public Set> incidentEdges(N node) { + return Collections.unmodifiableSet(super.incidentEdges(node)); + } + /** * A builder for creating {@link ImmutableGraph} instances, especially {@code static final} * graphs. Example: diff --git a/guava/src/com/google/common/graph/ImmutableNetwork.java b/guava/src/com/google/common/graph/ImmutableNetwork.java index e210be0d5275..6d1dc29336f2 100644 --- a/guava/src/com/google/common/graph/ImmutableNetwork.java +++ b/guava/src/com/google/common/graph/ImmutableNetwork.java @@ -25,7 +25,9 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.errorprone.annotations.InlineMe; +import java.util.Collections; import java.util.Map; +import java.util.Set; /** * A {@link Network} whose elements and structural relationships will never change. Instances of @@ -79,6 +81,36 @@ public ImmutableGraph asGraph() { return new ImmutableGraph<>(super.asGraph()); // safe because the view is effectively immutable } + @Override + public Set nodes() { + return Collections.unmodifiableSet(super.nodes()); + } + + @Override + public Set edges() { + return Collections.unmodifiableSet(super.edges()); + } + + @Override + public Set adjacentNodes(N node) { + return Collections.unmodifiableSet(super.adjacentNodes(node)); + } + + @Override + public Set predecessors(N node) { + return Collections.unmodifiableSet(super.predecessors(node)); + } + + @Override + public Set successors(N node) { + return Collections.unmodifiableSet(super.successors(node)); + } + + @Override + public Set incidentEdges(N node) { + return Collections.unmodifiableSet(super.incidentEdges(node)); + } + private static Map> getNodeConnections(Network network) { // ImmutableMap.Builder maintains the order of the elements as inserted, so the map will have // whatever ordering the network's nodes do, so ImmutableSortedMap is unnecessary even if the diff --git a/guava/src/com/google/common/graph/ImmutableValueGraph.java b/guava/src/com/google/common/graph/ImmutableValueGraph.java index 8f09d6f9096e..9ec94daa507e 100644 --- a/guava/src/com/google/common/graph/ImmutableValueGraph.java +++ b/guava/src/com/google/common/graph/ImmutableValueGraph.java @@ -26,6 +26,8 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.Immutable; import com.google.errorprone.annotations.InlineMe; +import java.util.Collections; +import java.util.Set; /** * A {@link ValueGraph} whose elements and structural relationships will never change. Instances of @@ -81,6 +83,36 @@ public ImmutableGraph asGraph() { return new ImmutableGraph<>(this); // safe because the view is effectively immutable } + @Override + public Set nodes() { + return Collections.unmodifiableSet(super.nodes()); + } + + @Override + public Set> edges() { + return Collections.unmodifiableSet(super.edges()); + } + + @Override + public Set adjacentNodes(N node) { + return Collections.unmodifiableSet(super.adjacentNodes(node)); + } + + @Override + public Set predecessors(N node) { + return Collections.unmodifiableSet(super.predecessors(node)); + } + + @Override + public Set successors(N node) { + return Collections.unmodifiableSet(super.successors(node)); + } + + @Override + public Set> incidentEdges(N node) { + return Collections.unmodifiableSet(super.incidentEdges(node)); + } + private static ImmutableMap> getNodeConnections( ValueGraph graph) { // ImmutableMap.Builder maintains the order of the elements as inserted, so the map will have