com.google.javascript.jscomp.graph.LinkedDirectedGraph Maven / Gradle / Ivy
Show all versions of closure-compiler-linter Show documentation
/*
* Copyright 2008 The Closure Compiler 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.javascript.jscomp.graph;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A directed graph using linked list within nodes to store edge information.
*
* This implementation favors directed graph operations inherited from
* DirectedGraph
.
* Operations from Graph
would tends to be slower.
*
*
* @param Value type that the graph node stores.
* @param Value type that the graph edge stores.
*/
public class LinkedDirectedGraph
extends DiGraph implements GraphvizGraph {
protected final Map> nodes = new LinkedHashMap<>();
@Override
public SubGraph newSubGraph() {
return new SimpleSubGraph<>(this);
}
public static LinkedDirectedGraph createWithoutAnnotations() {
return new LinkedDirectedGraph<>(false, false);
}
public static LinkedDirectedGraph create() {
return new LinkedDirectedGraph<>(true, true);
}
private final boolean useNodeAnnotations;
private final boolean useEdgeAnnotations;
protected LinkedDirectedGraph(
boolean useNodeAnnotations, boolean useEdgeAnnotations) {
this.useNodeAnnotations = useNodeAnnotations;
this.useEdgeAnnotations = useEdgeAnnotations;
}
@Override
public void connect(N srcValue, E edgeValue, N destValue) {
LinkedDirectedGraphNode src = getNodeOrFail(srcValue);
LinkedDirectedGraphNode dest = getNodeOrFail(destValue);
LinkedDirectedGraphEdge edge =
useEdgeAnnotations
? new AnnotatedLinkedDirectedGraphEdge<>(src, edgeValue, dest)
: new LinkedDirectedGraphEdge<>(src, edgeValue, dest);
src.getOutEdges().add(edge);
dest.getInEdges().add(edge);
}
// TODO(johnlenz): make this part of the general graph interface.
/**
* DiGraphNode look ups can be expensive for a large graph operation, prefer this
* method if you have the DiGraphNode available.
*/
public void connect(
DiGraphNode src,
E edgeValue,
DiGraphNode dest) {
LinkedDirectedGraphEdge edge =
useEdgeAnnotations
? new AnnotatedLinkedDirectedGraphEdge<>(src, edgeValue, dest)
: new LinkedDirectedGraphEdge<>(src, edgeValue, dest);
src.getOutEdges().add(edge);
dest.getInEdges().add(edge);
}
// TODO(johnlenz): make this part of the general graph interface.
/**
* DiGraphNode look ups can be expensive for a large graph operation, prefer this
* method if you have the DiGraphNode available.
*/
public void connectIfNotConnectedInDirection(N srcValue, E edgeValue, N destValue) {
LinkedDirectedGraphNode src = createDirectedGraphNode(srcValue);
LinkedDirectedGraphNode dest = createDirectedGraphNode(destValue);
if (!this.isConnectedInDirection(src, Predicates.equalTo(edgeValue), dest)) {
this.connect(src, edgeValue, dest);
}
}
@Override
public void disconnect(N n1, N n2) {
disconnectInDirection(n1, n2);
disconnectInDirection(n2, n1);
}
@Override
public void disconnectInDirection(N srcValue, N destValue) {
LinkedDirectedGraphNode src = getNodeOrFail(srcValue);
LinkedDirectedGraphNode dest = getNodeOrFail(destValue);
for (DiGraphEdge, E> edge : getDirectedGraphEdges(srcValue, destValue)) {
src.getOutEdges().remove(edge);
dest.getInEdges().remove(edge);
}
}
@Override
public Iterable> getDirectedGraphNodes() {
return Collections.>unmodifiableCollection(
nodes.values());
}
@Override
public DiGraphNode getDirectedGraphNode(N nodeValue) {
return nodes.get(nodeValue);
}
@Override
public GraphNode getNode(N nodeValue) {
return getDirectedGraphNode(nodeValue);
}
@Override
public List> getInEdges(N nodeValue) {
LinkedDirectedGraphNode node = getNodeOrFail(nodeValue);
return Collections.unmodifiableList(node.getInEdges());
}
@Override
public List> getOutEdges(N nodeValue) {
LinkedDirectedGraphNode node = getNodeOrFail(nodeValue);
return Collections.unmodifiableList(node.getOutEdges());
}
@Override
public LinkedDirectedGraphNode createDirectedGraphNode(N nodeValue) {
LinkedDirectedGraphNode node =
nodes.computeIfAbsent(
nodeValue,
(N k) ->
useNodeAnnotations
? new AnnotatedLinkedDirectedGraphNode(k)
: new LinkedDirectedGraphNode(k));
return node;
}
@Override
public List> getEdges(N n1, N n2) {
// Since this is a method from a generic graph, edges from both
// directions must be added to the returning list.
List> forwardEdges = getDirectedGraphEdges(n1, n2);
List> backwardEdges = getDirectedGraphEdges(n2, n1);
int totalSize = forwardEdges.size() + backwardEdges.size();
List> edges = new ArrayList<>(totalSize);
edges.addAll(forwardEdges);
edges.addAll(backwardEdges);
return edges;
}
@Override
public List> getEdges() {
List> result = new ArrayList<>();
for (DiGraphNode node : nodes.values()) {
result.addAll(node.getOutEdges());
}
return Collections.unmodifiableList(result);
}
@Override
public GraphEdge getFirstEdge(N n1, N n2) {
DiGraphNode dNode1 = getNodeOrFail(n1);
DiGraphNode dNode2 = getNodeOrFail(n2);
for (DiGraphEdge outEdge : dNode1.getOutEdges()) {
if (outEdge.getDestination() == dNode2) {
return outEdge;
}
}
for (DiGraphEdge outEdge : dNode2.getOutEdges()) {
if (outEdge.getDestination() == dNode1) {
return outEdge;
}
}
return null;
}
@Override
public DiGraphNode createNode(N value) {
return createDirectedGraphNode(value);
}
@Override
public List> getDirectedGraphEdges(N n1, N n2) {
DiGraphNode dNode1 = getNodeOrFail(n1);
DiGraphNode dNode2 = getNodeOrFail(n2);
List> edges = new ArrayList<>();
for (DiGraphEdge outEdge : dNode1.getOutEdges()) {
if (outEdge.getDestination() == dNode2) {
edges.add(outEdge);
}
}
return edges;
}
/**
* DiGraphNode look ups can be expensive for a large graph operation, prefer the
* version below that takes DiGraphNodes, if you have them available.
*/
@Override
public boolean isConnectedInDirection(N n1, N n2) {
return isConnectedInDirection(n1, Predicates.alwaysTrue(), n2);
}
/**
* DiGraphNode look ups can be expensive for a large graph operation, prefer the
* version below that takes DiGraphNodes, if you have them available.
*/
@Override
public boolean isConnectedInDirection(N n1, E edgeValue, N n2) {
return isConnectedInDirection(n1, Predicates.equalTo(edgeValue), n2);
}
/**
* DiGraphNode look ups can be expensive for a large graph operation, prefer this
* method if you have the DiGraphNodes available.
*/
public boolean isConnectedInDirection(
DiGraphNode dNode1,
Predicate edgeMatcher,
DiGraphNode dNode2) {
// Verify the nodes.
List> outEdges = dNode1.getOutEdges();
int outEdgesLen = outEdges.size();
List> inEdges = dNode2.getInEdges();
int inEdgesLen = inEdges.size();
// It is possible that there is a large assymmetry between the nodes, so pick the direction
// to search based on the shorter list since the edge lists should be symmetric.
if (outEdgesLen < inEdgesLen) {
for (int i = 0; i < outEdgesLen; i++) {
DiGraphEdge outEdge = outEdges.get(i);
if (outEdge.getDestination() == dNode2
&& edgeMatcher.apply(outEdge.getValue())) {
return true;
}
}
} else {
for (int i = 0; i < inEdgesLen; i++) {
DiGraphEdge inEdge = inEdges.get(i);
if (inEdge.getSource() == dNode1
&& edgeMatcher.apply(inEdge.getValue())) {
return true;
}
}
}
return false;
}
private boolean isConnectedInDirection(N n1, Predicate edgeMatcher, N n2) {
// Verify the nodes.
DiGraphNode dNode1 = getNodeOrFail(n1);
DiGraphNode dNode2 = getNodeOrFail(n2);
return isConnectedInDirection(dNode1, edgeMatcher, dNode2);
}
@Override
public List> getDirectedPredNodes(N nodeValue) {
return getDirectedPredNodes(nodes.get(nodeValue));
}
@Override
public List> getDirectedPredNodes(DiGraphNode dNode) {
checkNotNull(dNode);
List> nodeList = new ArrayList<>(dNode.getInEdges().size());
for (DiGraphEdge edge : dNode.getInEdges()) {
nodeList.add(edge.getSource());
}
return nodeList;
}
@Override
public List> getDirectedSuccNodes(N nodeValue) {
return getDirectedSuccNodes(nodes.get(nodeValue));
}
@Override
public List> getDirectedSuccNodes(
DiGraphNode dNode) {
checkNotNull(dNode);
List> nodeList =
new ArrayList<>(dNode.getOutEdges().size());
for (DiGraphEdge edge : dNode.getOutEdges()) {
nodeList.add(edge.getDestination());
}
return nodeList;
}
@Override
public List getGraphvizEdges() {
List edgeList = new ArrayList<>();
for (LinkedDirectedGraphNode node : nodes.values()) {
for (DiGraphEdge edge : node.getOutEdges()) {
edgeList.add((LinkedDirectedGraphEdge) edge);
}
}
return edgeList;
}
@Override
public List getGraphvizNodes() {
List nodeList = new ArrayList<>(nodes.size());
for (LinkedDirectedGraphNode node : nodes.values()) {
nodeList.add(node);
}
return nodeList;
}
@Override
public String getName() {
return "LinkedGraph";
}
@Override
public boolean isDirected() {
return true;
}
@Override
public Collection> getNodes() {
return Collections.>unmodifiableCollection(nodes.values());
}
@Override
public int getNodeCount() {
return nodes.size();
}
@Override
public List> getNeighborNodes(N value) {
DiGraphNode node = getDirectedGraphNode(value);
List> result = new ArrayList<>(
node.getInEdges().size() + node.getOutEdges().size());
for (DiGraphEdge inEdge : node.getInEdges()) {
result.add(inEdge.getSource());
}
for (DiGraphEdge outEdge : node.getOutEdges()) {
result.add(outEdge.getDestination());
}
return result;
}
@Override
public int getNodeDegree(N value) {
DiGraphNode node = getNodeOrFail(value);
return node.getInEdges().size() + node.getOutEdges().size();
}
/**
* A directed graph node that stores outgoing edges and incoming edges as an
* list within the node itself.
*/
public static class LinkedDirectedGraphNode implements DiGraphNode,
GraphvizNode {
List> inEdgeList = new ArrayList<>();
List> outEdgeList = new ArrayList<>();
protected final N value;
/**
* Constructor
*
* @param nodeValue Node's value.
*/
LinkedDirectedGraphNode(N nodeValue) {
this.value = nodeValue;
}
@Override
public N getValue() {
return value;
}
@Override
public A getAnnotation() {
throw new UnsupportedOperationException(
"Graph initialized with node annotations turned off");
}
@Override
public void setAnnotation(Annotation data) {
throw new UnsupportedOperationException(
"Graph initialized with node annotations turned off");
}
@Override
public String getColor() {
return "white";
}
@Override
public String getId() {
return "LDN" + hashCode();
}
@Override
public String getLabel() {
return String.valueOf(value);
}
@Override
public String toString() {
return getLabel();
}
@Override
public List> getInEdges() {
return inEdgeList;
}
@Override
public List> getOutEdges() {
return outEdgeList;
}
}
/**
* A directed graph node with annotations.
*/
static class AnnotatedLinkedDirectedGraphNode
extends LinkedDirectedGraphNode {
protected Annotation annotation;
/**
* @param nodeValue Node's value.
*/
AnnotatedLinkedDirectedGraphNode(N nodeValue) {
super(nodeValue);
}
@SuppressWarnings("unchecked")
@Override
public A getAnnotation() {
return (A) annotation;
}
@Override
public void setAnnotation(Annotation data) {
annotation = data;
}
}
/**
* A directed graph edge that stores the source and destination nodes at each
* edge.
*/
static class LinkedDirectedGraphEdge implements DiGraphEdge,
GraphvizEdge {
private DiGraphNode sourceNode;
private DiGraphNode destNode;
protected final E value;
/**
* Constructor.
*
* @param edgeValue Edge Value.
*/
LinkedDirectedGraphEdge(DiGraphNode sourceNode,
E edgeValue, DiGraphNode destNode) {
this.value = edgeValue;
this.sourceNode = sourceNode;
this.destNode = destNode;
}
@Override
public DiGraphNode getSource() {
return sourceNode;
}
@Override
public DiGraphNode getDestination() {
return destNode;
}
@Override
public void setDestination(DiGraphNode node) {
destNode = node;
}
@Override
public void setSource(DiGraphNode node) {
sourceNode = node;
}
@Override
public E getValue() {
return value;
}
@Override
public A getAnnotation() {
throw new UnsupportedOperationException(
"Graph initialized with edge annotations turned off");
}
@Override
public void setAnnotation(Annotation data) {
throw new UnsupportedOperationException(
"Graph initialized with edge annotations turned off");
}
@Override
public String getColor() {
return "black";
}
@Override
public String getLabel() {
return String.valueOf(value);
}
@Override
public String getNode1Id() {
return ((LinkedDirectedGraphNode) sourceNode).getId();
}
@Override
public String getNode2Id() {
return ((LinkedDirectedGraphNode) destNode).getId();
}
@Override
public String toString() {
return sourceNode + " -> " + destNode;
}
@Override
public GraphNode getNodeA() {
return sourceNode;
}
@Override
public GraphNode getNodeB() {
return destNode;
}
}
/**
* A directed graph edge that stores the source and destination nodes at each
* edge.
*/
static class AnnotatedLinkedDirectedGraphEdge
extends LinkedDirectedGraphEdge {
protected Annotation annotation;
/**
* Constructor.
*
* @param edgeValue Edge Value.
*/
AnnotatedLinkedDirectedGraphEdge(DiGraphNode sourceNode,
E edgeValue, DiGraphNode destNode) {
super(sourceNode, edgeValue, destNode);
}
@SuppressWarnings("unchecked")
@Override
public A getAnnotation() {
return (A) annotation;
}
@Override
public void setAnnotation(Annotation data) {
annotation = data;
}
}
}