289 lines
8.4 KiB
Ruby
289 lines
8.4 KiB
Ruby
# Copyright (c) HashiCorp, Inc.
|
|
# SPDX-License-Identifier: BUSL-1.1
|
|
|
|
require "rgl/adjacency"
|
|
require "rgl/traversal"
|
|
require "rgl/dijkstra"
|
|
require "rgl/topsort"
|
|
|
|
module VagrantPlugins
|
|
module CommandServe
|
|
class Mappers
|
|
module Internal
|
|
class Graph < RGL::DirectedAdjacencyGraph
|
|
|
|
# This iterator is used for detecting and breaking cycles
|
|
# discovered within the graph using the DFS graph visitor
|
|
# concept
|
|
class CycleDeletorIterator < RGL::DFSVisitor
|
|
# Create a new iterator instance. Store the graph
|
|
# for later inspection use
|
|
def initialize(graph, *_)
|
|
@graph = graph
|
|
super
|
|
end
|
|
|
|
# Examines the vertex to detect any cycles. If an
|
|
# adjacent edge has already been visited then we
|
|
# remove the edge to break the cycle.
|
|
def handle_examine_vertex(u)
|
|
graph.each_adjacent(u) do |v|
|
|
if v.is_a?(Vertex::Output) && color_map[v] != :WHITE
|
|
graph.remove_edge(u, v)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
autoload :Mappers, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/mappers").to_s
|
|
autoload :Search, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/search").to_s
|
|
autoload :Vertex, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/vertex").to_s
|
|
autoload :WeightedVertex, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/weighted_vertex").to_s
|
|
|
|
include Util::HasLogger
|
|
|
|
# Default weight used for weighted vetices
|
|
# when no weight is provided
|
|
DEFAULT_WEIGHT = 1000
|
|
|
|
def initialize(*_)
|
|
@vertex_map = {}
|
|
super
|
|
end
|
|
|
|
def initialize_copy(orig)
|
|
super
|
|
@vertex_map = orig.instance_eval { @vertex_map }.dup
|
|
@vertices_dict = @vertices_dict.dup
|
|
@vertices_dict.keys.each do |v|
|
|
edges = @vertices_dict[v]
|
|
@vertices_dict[v] = {in: edges[:in].dup, out: edges[:out].dup}
|
|
end
|
|
end
|
|
|
|
def to_a
|
|
@vertices_dict.keys
|
|
end
|
|
|
|
def each_out_vertex(v, &block)
|
|
v = vertex_for(v)
|
|
if !has_vertex?(v)
|
|
raise "No vertex `#{v}' found in graph"
|
|
end
|
|
@vertices_dict[v][:out].each(&block)
|
|
end
|
|
alias :each_adjacent :each_out_vertex
|
|
|
|
def out_vertices(v)
|
|
v = vertex_for(v)
|
|
if !has_vertex?(v)
|
|
raise "No vertex `#{v}' found in graph"
|
|
end
|
|
@vertices_dict[v][:out].to_a
|
|
end
|
|
alias :adjacent_vertices :out_vertices
|
|
|
|
def out_degree(v)
|
|
v = vertex_for(v)
|
|
if !has_vertex?(v)
|
|
raise "No vertex `#{v}' found in graph"
|
|
end
|
|
@vertices_dict[v][:out].size
|
|
end
|
|
|
|
def each_in_vertex(v, &block)
|
|
v = vertex_for(v)
|
|
if !has_vertex?(v)
|
|
raise "No vertex `#{v}' found in graph"
|
|
end
|
|
@vertices_dict[v][:in].each(&block)
|
|
end
|
|
|
|
def in_vertices(v)
|
|
v = vertex_for(v)
|
|
if !has_vertex?(v)
|
|
raise "No vertex `#{v}' found in graph"
|
|
end
|
|
@vertices_dict[v][:in].to_a
|
|
end
|
|
|
|
def in_degree(v)
|
|
v = vertex_for(v)
|
|
if !has_vertex?(v)
|
|
raise "No vertex `#{v}' found in graph"
|
|
end
|
|
@vertices[v][:in].size
|
|
end
|
|
|
|
def vertices
|
|
@vertices_dict.keys
|
|
end
|
|
|
|
def each_vertex(&block)
|
|
vertices.each(&block)
|
|
end
|
|
|
|
def num_vertices
|
|
@vertices_dict.size
|
|
end
|
|
|
|
def num_edges
|
|
@vertices_dict.each_value.inject(0) do |count, edges|
|
|
count + edges[:out].size
|
|
end
|
|
end
|
|
|
|
def has_edge?(u, v)
|
|
u = vertex_for(u)
|
|
v = vertex_for(v)
|
|
has_vertex?(u) && @vertices_dict[u][0].include?(v)
|
|
end
|
|
|
|
def add_vertex(v)
|
|
if !v.is_a?(Vertex)
|
|
raise TypeError,
|
|
"Expected type `Vertex', got `#{v.class}'"
|
|
end
|
|
if @vertex_map.key?(v.hash_code)
|
|
v = @vertex_map[v.hash_code]
|
|
else
|
|
if !v.is_a?(WeightedVertex)
|
|
v = WeightedVertex.new(v, weight: DEFAULT_WEIGHT)
|
|
end
|
|
@vertex_map[v.hash_code] = v
|
|
end
|
|
|
|
@vertices_dict[v] ||= {
|
|
in: @edgelist_class.new,
|
|
out: @edgelist_class.new,
|
|
}
|
|
v
|
|
end
|
|
|
|
def add_edge(u, v)
|
|
u = add_vertex(u) # ensure key
|
|
v = add_vertex(v) # ensure key
|
|
basic_add_edge(u, v)
|
|
end
|
|
|
|
def remove_vertex(v)
|
|
v = vertex_for(v)
|
|
if has_vertex?(v)
|
|
edges = @vertices_dict[v]
|
|
@vertices_dict.delete(v)
|
|
edges[:in].each do |parent|
|
|
@vertices_dict[parent][:out].delete(v)
|
|
end
|
|
edges[:out].each do |child|
|
|
@vertices_dict[child][:in].delete(v)
|
|
end
|
|
@vertex_map.delete(v.hash_code)
|
|
end
|
|
v
|
|
end
|
|
|
|
def remove_edge(u, v)
|
|
u = vertex_for(u)
|
|
v = vertex_for(v)
|
|
if has_vertex?(u) && has_vertex?(v)
|
|
if @vertices_dict[u][:out].delete?(v)
|
|
@vertices_dict[v][:in].delete(u)
|
|
end
|
|
end
|
|
self
|
|
end
|
|
|
|
def edgelist_class=(klass)
|
|
@vertices_dict.keys.each do |v|
|
|
edges = @vertices_dict[v]
|
|
@vertices_dict[v] = {
|
|
in: klass.new(edges[:in].to_a),
|
|
out: klass.new(edges[:out].to_a),
|
|
}
|
|
end
|
|
self
|
|
end
|
|
|
|
def reverse
|
|
result = clone
|
|
result.reverse!
|
|
end
|
|
|
|
def reverse!
|
|
@vertices_dict.keys.each do |v|
|
|
edges = @vertices_dict[v]
|
|
@vertices_dict[v] = {
|
|
in: edges[:out],
|
|
out: edges[:in],
|
|
}
|
|
end
|
|
self
|
|
end
|
|
|
|
def break_cycles!(src)
|
|
depth_first_visit(src, CycleDeletorIterator.new(self)) { true }
|
|
end
|
|
|
|
def shortest_path(source, target)
|
|
dijkstra_shortest_path(edge_weights_map, source, target)
|
|
end
|
|
|
|
def shortest_paths(source, target)
|
|
dijkstra_shortest_paths(edge_weights_map, source, target)
|
|
end
|
|
|
|
def edge_weights_map
|
|
Hash.new.tap do |edge_map|
|
|
each_edge do |*edges|
|
|
w = edges.map(&:weight).inject(&:+)
|
|
if edges.first.respond_to?(:type) && edges.last.respond_to?(:type)
|
|
if edges.first.type != edges.last.type
|
|
w += 200
|
|
extra = edges.first.type.ancestors.index(edges.last.type)
|
|
if extra.nil?
|
|
extra = edges.last.type.ancestors.index(edges.first.type)
|
|
end
|
|
w += extra.to_i
|
|
end
|
|
end
|
|
edge_map[edges] = w
|
|
end
|
|
end
|
|
end
|
|
|
|
def vertex_for(v)
|
|
@vertex_map[v.hash_code]
|
|
end
|
|
|
|
def to_s
|
|
"<#{self.class.name}:#{object_id} num_vertices=#{vertices.size}>"
|
|
end
|
|
|
|
def inspect
|
|
vinfo = vertices.map do |v|
|
|
ins = in_vertices(v).map do |iv|
|
|
" -> #{iv}"
|
|
end.join("\n")
|
|
outs = out_vertices(v).map do |ov|
|
|
" <- #{ov}"
|
|
end.join("\n")
|
|
" " + [v].tap { |content|
|
|
content << ins if !ins.empty?
|
|
content << outs if !outs.empty?
|
|
}.join("\n")
|
|
end.join("\n")
|
|
"<#{self.class.name}:#{object_id} num_vertices=#{vertices.size} vertices=\n#{vinfo}\n>"
|
|
end
|
|
|
|
protected
|
|
|
|
def basic_add_edge(u, v)
|
|
@vertices_dict[u][:out].add(v)
|
|
@vertices_dict[v][:in].add(u)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|