/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.core;
import com.google.common.base.Joiner;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* @author bchang
*/
public class UnidirectionalCyclicGraph<E> {
private final Map<String, Node<E>> _nodes = new HashMap<>();
public E get(@NotNull String id) {
return getNode(id).getValue();
}
@Nullable
protected Node<E> getNode(@NotNull String id) {
if (id.contains("/")) {
// super hacky way for determining if it's a Carbon-style notation for a jar dependency, which we no longer have in Diamond
// the problem is that we use ModuleGraph to parse Carbon modules.xml files, which have a slightly different schema
return null;
}
Node<E> node = _nodes.get(id);
if (node == null) {
throw new IllegalArgumentException("could not find graph node for id \"" + id + "\"");
}
return node;
}
public void registerNode(String id, Node<E> node) {
if (_nodes.containsKey(id)) {
throw new IllegalArgumentException("graph already contains an item with id \"" + id + "\"");
}
_nodes.put(id, node);
}
public void resolveLinks() {
for (Node<E> node : _nodes.values()) {
for (String linkId : node.getLinks()) {
if (linkId.contains("/")) {
// super hacky way for determining if it's a Carbon-style notation for a jar dependency, which we no longer have in Diamond
// the problem is that we use ModuleGraph to parse Carbon modules.xml files, which have a slightly different schema
continue;
}
if (!_nodes.containsKey(linkId)) {
throw new IllegalStateException("node \"" + node.getId() + "\" contains a link with ID \"" + linkId + "\" which couldn't be found");
}
}
}
for (Node<E> node : _nodes.values()) {
searchDeepForCircularPath(node, node, new HashSet<Node<E>>(), new ArrayList<Node<E>>());
}
}
@NotNull
public LinkedHashSet<Node<E>> buildGraphPath(@NotNull String id) {
return buildGraphPathImpl(id, null, null);
}
@NotNull
public LinkedHashSet<Node<E>> buildGraphPath(@NotNull String id, NodeLinkPredicate<E> predicate) {
return buildGraphPathImpl(id, predicate, null);
}
@NotNull
private LinkedHashSet<Node<E>> buildGraphPathImpl(@NotNull String id, @Nullable NodeLinkPredicate<E> predicate, @Nullable String avoidId) {
LinkedHashSet<Node<E>> list = new LinkedHashSet<>();
Node<E> node = getNode(id);
if (node != null) {
for (String linkId : node.getLinks()) {
if (predicate == null || predicate.acceptLink(node, getNode(linkId))) {
// we check for avoidId in order to avoid revisiting this node while visiting a cross dependency
if (avoidId == null || !avoidId.equals(linkId)) {
LinkedHashSet<Node<E>> linkList = buildGraphPathImpl(linkId, predicate, id);
list.addAll(linkList);
}
}
}
list.add(node);
}
return list;
}
public Collection<Node<E>> getAll() {
return _nodes.values();
}
private void searchDeepForCircularPath(@NotNull Node<E> node, Node<E> badNode, @NotNull Set<Node<E>> visited, @NotNull List<Node<E>> path) {
path.add(node);
for (String linkId : node.getLinks()) {
Node<E> linkNode = getNode(linkId);
if (linkNode == null) {
continue;
}
if (!visited.contains(linkNode)) {
if (linkNode.equals(badNode)) {
path.add(linkNode);
throw new IllegalStateException(Joiner.on(" \u2192 ").join(path));
}
visited.add(linkNode);
searchDeepForCircularPath(linkNode, badNode, visited, path);
}
}
path.remove(path.size() - 1);
}
public static interface NodeLinkPredicate<E> {
boolean acceptLink(Node<E> node, Node<E> linkNode);
}
public static class Node<E> implements Comparable<Node<E>> {
private final String _id;
private final E _value;
private final TreeSet<String> _links = new TreeSet<>();
public Node(String id, E value) {
_id = id;
_value = value;
}
public String getId() {
return _id;
}
public E getValue() {
return _value;
}
public void addLink(String linkId) {
if (_id.equals(linkId)) {
throw new IllegalArgumentException("a node cannot link to itself");
}
_links.add(linkId);
}
@NotNull
public SortedSet<String> getLinks() {
return _links;
}
public int hashCode() {
return _id.hashCode();
}
public boolean equals(@Nullable Object o) {
return o != null && (o == this || o instanceof Node && _id.equals(((Node) o)._id));
}
public int compareTo(@NotNull Node<E> that) {
return _id.compareTo(that._id);
}
public String toString() {
return getId();
}
}
}