namespace TeamCity.Docker.Generic { using System; using System.Collections.Generic; using System.Linq; using System.Text; using IoC; internal class Graph : IGraph { [NotNull] private readonly IEqualityComparer _nodeComparer; [NotNull] private readonly IEqualityComparer _linkComparer; [NotNull] private readonly HashSet> _nodes; [NotNull] private readonly HashSet> _links; public Graph() :this(EqualityComparer.Default, EqualityComparer.Default) { } // ReSharper disable once MemberCanBePrivate.Global public Graph( [NotNull] IEqualityComparer nodeComparer, [NotNull] IEqualityComparer linkComparer) { _nodeComparer = nodeComparer ?? throw new ArgumentNullException(nameof(nodeComparer)); _linkComparer = linkComparer ?? throw new ArgumentNullException(nameof(linkComparer)); _nodes = new HashSet>(); _links = new HashSet>(); } public IEnumerable> Nodes => _nodes; public int NodesCount => _nodes.Count; public IEnumerable> Links => _links; public int LinksCount => _links.Count; public bool TryAddNode(TNode value, out INode node) { if (value == null) throw new ArgumentNullException(nameof(value)); var newNode = new Node(value, _nodeComparer); return TryAddNode(newNode, out node); } public bool TryRemoveNode(INode node) { if (node == null) throw new ArgumentNullException(nameof(node)); return _nodes.Remove(node); } public bool TryAddLink(INode from, TLink value, INode to, out ILink link) { if (from == null) throw new ArgumentNullException(nameof(from)); if (value == null) throw new ArgumentNullException(nameof(value)); if (to == null) throw new ArgumentNullException(nameof(to)); TryAddNode(from, out from); TryAddNode(to, out to); var newLink = link = new Link(from, value, to, _linkComparer); var success = _links.Add(newLink); if (!success) { link = _links.Single(i => i.Equals(newLink)); } return success; } public bool TryRemoveLink(ILink link) { if (link == null) throw new ArgumentNullException(nameof(link)); return _links.Remove(link); } public IGraph Copy(Predicate> filter) { var clone = new Graph(_nodeComparer, _linkComparer); var newNodes = Nodes .Where(node => filter(node)) .Select(node => { clone.TryAddNode(node.Value, out var newNode); return new { node, newNode}; }) .ToDictionary(i => i.node, i => i.newNode); foreach (var link in Links) { if (newNodes.TryGetValue(link.From, out _) && newNodes.TryGetValue(link.To, out _)) { clone.TryAddLink(newNodes[link.From], link.Value, newNodes[link.To], out var _); } } return clone; } public override string ToString() { var dict = new Dictionary, int>(); var sb = new StringBuilder(); sb.AppendLine("digraph g {"); foreach (var node in _nodes) { sb.AppendLine($"{GetId(dict, node)} [label=\"{node.Value}\"];"); } foreach (var link in _links) { sb.AppendLine($"{GetId(dict, link.From)} -> {GetId(dict, link.To)};"); } sb.AppendLine("}"); return sb.ToString(); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } var other = (Graph) obj; return _nodes.SetEquals(other._nodes) && _links.SetEquals(other._links); } public override int GetHashCode() { unchecked { return _nodes.Sum(node => node.GetHashCode()) + _links.Sum(link => link.GetHashCode()); } } private bool TryAddNode(INode newNode, out INode node) { var success = _nodes.Add(newNode); node = success ? newNode : _nodes.Single(i => i.Equals(newNode)); return success; } private static int GetId(IDictionary, int> dictionary, INode node) { if(dictionary.TryGetValue(node, out var id)) { return id; } id = dictionary.Any() ? dictionary.Values.Max() + 1 : 0; dictionary.Add(node, id); return id; } private class Node: INode { [NotNull] private readonly IEqualityComparer _nodeComparer; public Node( TNode value, [NotNull] IEqualityComparer nodeComparer) { if (value == null) { throw new ArgumentNullException(nameof(value)); } _nodeComparer = nodeComparer ?? throw new ArgumentNullException(nameof(nodeComparer)); Value = value; } public TNode Value { get; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } var other = (Node) obj; return _nodeComparer.Equals(Value, other.Value); } public override int GetHashCode() => _nodeComparer.GetHashCode(Value); public override string ToString() => Value.ToString(); } private class Link: ILink { private readonly IEqualityComparer _linkComparer; public Link( [NotNull] INode from, [NotNull] TLink value, [NotNull] INode to, [NotNull] IEqualityComparer linkComparer) { if (value == null) { throw new ArgumentNullException(nameof(value)); } _linkComparer = linkComparer ?? throw new ArgumentNullException(nameof(linkComparer)); From = from ?? throw new ArgumentNullException(nameof(from)); To = to ?? throw new ArgumentNullException(nameof(to)); Value = value; } public INode From { get; } public TLink Value { get; } public INode To { get; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } var other = (Link) obj; return From.Equals(other.From) && _linkComparer.Equals(Value, other.Value) && To.Equals(other.To); } public override int GetHashCode() { unchecked { var hashCode = From.GetHashCode(); hashCode = (hashCode * 397) ^ _linkComparer.GetHashCode(Value); hashCode = (hashCode * 397) ^ To.GetHashCode(); return hashCode; } } public override string ToString() => $"{From} -- {Value} --> {To}"; } } }