DependencyGraph.hpp
Go to the documentation of this file.
1 // @formatter:off
2 //
3 // Balau core C++ library
4 //
5 // Copyright (C) 2008 Bora Software (contact@borasoftware.com)
6 //
7 // Licensed under the Boost Software License - Version 1.0 - August 17th, 2003.
8 // See the LICENSE file for the full license text.
9 //
10 // See the Boost Graph dependency example for inspiration and background information.
11 // https://www.boost.org/doc/libs/1_68_0/libs/graph/doc/file_dependency_example.html
12 //
13 // Copyright 1997, 1998, 1999, 2000 University of Notre Dame.
14 // Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
15 //
16 // Distributed under the Boost Software License, Version 1.0. (See
17 // accompanying file LICENSE_1_0.txt or copy at
18 // http://www.boost.org/LICENSE_1_0.txt)
19 //
20 
26 
27 #ifndef COM_BORA_SOFTWARE__BALAU_CONTAINER__DEPENDENCY_GRAPH
28 #define COM_BORA_SOFTWARE__BALAU_CONTAINER__DEPENDENCY_GRAPH
29 
30 #include <Balau/Container/Impl/ContainerLogger.hpp>
32 
33 #include <boost/graph/adjacency_list.hpp>
34 #include <boost/graph/depth_first_search.hpp>
35 #include <boost/graph/breadth_first_search.hpp>
36 #include <boost/graph/topological_sort.hpp>
37 #include <boost/graph/visitors.hpp>
38 
39 #include <iomanip>
40 
41 // Avoid false positive.
42 #pragma clang diagnostic push
43 #pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
44 
45 namespace Balau::Container {
46 
87 template <typename VertexT = boost::no_property, typename EdgeT = boost::no_property, typename GraphT = boost::no_property>
89  private: using Graph = boost::adjacency_list<boost::setS, boost::vecS, boost::bidirectionalS, VertexT, EdgeT, GraphT>;
90  private: using Vertex = typename Graph::vertex_descriptor;
91  private: using Edge = typename Graph::edge_descriptor;
92  private: using VertexIterator = typename Graph::vertex_iterator;
93  private: using InEdgeIterator = typename Graph::in_edge_iterator;
94  private: using OutEdgeIterator = typename Graph::out_edge_iterator;
95 
99  public: class iterator {
103  public: iterator(const iterator & copy)
104  : vertexIter(copy.vertexIter) {}
105 
109  public: iterator & operator = (const iterator & copy) {
110  vertexIter = copy.vertexIter;
111  return *this;
112  }
113 
117  public: iterator operator ++ (int) {
118  iterator ret = *this;
119  ++vertexIter;
120  return ret;
121  }
122 
126  public: iterator & operator ++ () {
127  ++vertexIter;
128  return *this;
129  }
130 
134  public: VertexT & operator * () {
135  return graph[*vertexIter];
136  }
137 
141  public: const VertexT & operator * () const {
142  return graph[*vertexIter];
143  }
144 
148  public: VertexT & operator -> () {
149  return &graph[*vertexIter];
150  }
151 
155  public: const VertexT & operator -> () const {
156  return &graph[*vertexIter];
157  }
158 
162  public: bool operator == (const iterator & rhs) const {
163  return vertexIter == rhs.vertexIter;
164  }
165 
169  public: bool operator != (const iterator & rhs) const {
170  return vertexIter != rhs.vertexIter;
171  }
172 
173  friend class DependencyGraph<VertexT>;
174 
175  private: explicit iterator(Graph & graph_, VertexIterator vertexIter_)
176  : graph(graph_)
177  , vertexIter(vertexIter_) {}
178 
179  private: Graph & graph;
180  private: VertexIterator vertexIter;
181  };
182 
186  public: class const_iterator {
190  public: const_iterator(const const_iterator & copy)
191  : vertexIter(copy.vertexIter) {}
192 
196  public: const_iterator & operator = (const const_iterator & copy) {
197  vertexIter = copy.vertexIter;
198  return *this;
199  }
200 
205  const_iterator ret = *this;
206  ++vertexIter;
207  return ret;
208  }
209 
214  ++vertexIter;
215  return *this;
216  }
217 
221  public: const VertexT & operator * () const {
222  return graph[*vertexIter];
223  }
224 
228  public: const VertexT & operator -> () const {
229  return &graph[*vertexIter];
230  }
231 
235  public: bool operator == (const const_iterator & rhs) const {
236  return vertexIter == rhs.vertexIter;
237  }
238 
242  public: bool operator != (const const_iterator & rhs) const {
243  return vertexIter != rhs.vertexIter;
244  }
245 
246  friend class DependencyGraph<VertexT>;
247 
248  private: explicit const_iterator(const Graph & graph_, VertexIterator vertexIter_)
249  : graph(graph_)
250  , vertexIter(vertexIter_) {}
251 
252  private: const Graph & graph;
253  private: VertexIterator vertexIter;
254  };
255 
257 
268  public: void addDependency(const VertexT & dependency, bool throwIfExists = true) {
269  if (reverseLookup.find(dependency) != reverseLookup.end()) {
270  if (throwIfExists) {
272  } else {
273  return;
274  }
275  }
276 
277  Vertex vertex = boost::add_vertex(graph);
278  reverseLookup[dependency] = vertex;
279  graph[vertex] = dependency;
280  }
281 
294  public: void removeDependency(const VertexT & dependency, bool throwIfNotExists = true) {
295  //logGraph("Before remove");
296 
297  auto itemIter = reverseLookup.find(dependency);
298 
299  if (itemIter == reverseLookup.end()) {
300  if (throwIfNotExists) {
302  } else {
303  return;
304  }
305  }
306 
307  const auto vertex = itemIter->second;
308  boost::clear_vertex(vertex, graph); // Removes edges to/from the vertex.
309  boost::remove_vertex(vertex, graph); // Removes the vertex.
310  rebuildReverseLookup();
311 
312  //logGraph("After rebuild");
313  }
314 
318  public: void addRelationship(const VertexT & independent, const VertexT & dependent) {
319  auto independentIter = reverseLookup.find(independent);
320  auto dependentIter = reverseLookup.find(dependent);
321 
322  if (independentIter == reverseLookup.end()) {
324  }
325 
326  if (dependentIter == reverseLookup.end()) {
328  }
329 
330  boost::add_edge(independentIter->second, dependentIter->second, graph);
331  }
332 
336  public: void addRelationship(const VertexT & independent, const VertexT & dependent, const EdgeT & edgeData) {
337  auto independentIter = reverseLookup.find(independent);
338  auto dependentIter = reverseLookup.find(dependent);
339 
340  if (independentIter == reverseLookup.end()) {
342  }
343 
344  if (dependentIter == reverseLookup.end()) {
346  }
347 
348  boost::add_edge(independentIter->second, dependentIter->second, edgeData, graph);
349  }
350 
354  public: void removeRelationship(const VertexT & independent, const VertexT & dependent) {
355  auto independentIter = reverseLookup.find(independent);
356  auto dependentIter = reverseLookup.find(dependent);
357 
358  if (independentIter == reverseLookup.end()) {
360  }
361 
362  if (dependentIter == reverseLookup.end()) {
364  }
365 
366  boost::remove_edge(independentIter->second, dependentIter->second, graph);
367  }
368 
369  public: EdgeT getRelation(const VertexT & independent, const VertexT & dependent) const {
370  auto independentIter = reverseLookup.find(independent);
371  auto dependentIter = reverseLookup.find(dependent);
372 
373  if (independentIter == reverseLookup.end()) {
375  }
376 
377  if (dependentIter == reverseLookup.end()) {
379  }
380 
381  auto independentVertex = independentIter->second;
382  auto dependentVertex = dependentIter->second;
383  auto e = boost::edge(independentVertex, dependentVertex, graph);
384 
385  if (!e.second) {
386  _ThrowBalauException_generateStackTrace
388  __FILE__, __LINE__, st, independent, dependent, ""
389  );
390  }
391 
392  return graph[e.first];
393  }
394 
398  public: bool hasDependency(const VertexT & dependency) const {
399  return reverseLookup.find(dependency) != reverseLookup.end();
400  }
401 
405  public: std::vector<VertexT> directDependenciesOf(const VertexT & dependency) const {
406  auto vertex = boost::vertex(reverseLookup.at(dependency), graph);
407  InEdgeIterator iter, end;
408  std::vector<VertexT> ret;
409 
410  for (boost::tie(iter, end) = boost::in_edges(vertex, graph); iter != end; ++iter) {
411  auto s = source(*iter, graph);
412  ret.emplace_back(graph[s]);
413  }
414 
415  return ret;
416  }
417 
423  public: const VertexT * lookupDependency(const VertexT & key) const {
424  auto iter = reverseLookup.find(key);
425 
426  if (iter != reverseLookup.end()) {
427  return &iter->first;
428  } else {
429  return nullptr;
430  }
431  }
432 
436  public: std::vector<VertexT> dependencyOrder() const {
437  std::vector<VertexT> ret;
438  std::list<Vertex> depOrder;
439  boost::topological_sort(graph, std::front_inserter(depOrder));
440 
441  for (auto vertex : depOrder) {
442  ret.push_back(graph[vertex]);
443  }
444 
445  return ret;
446  }
447 
454  public: std::vector<std::vector<VertexT>> parallelDependencyOrder() const {
455  std::list<Vertex> depOrder;
456  std::vector<Vertex> order(reverseLookup.size(), 0);
457  boost::topological_sort(graph, std::front_inserter(depOrder));
458  size_t levelCount = 0;
459 
460  for (auto vertex : depOrder) {
461  if (in_degree(vertex, graph) > 0) {
462  size_t maximum = 0;
463  InEdgeIterator edgeIter, edgeEnd;
464 
465  for (boost::tie(edgeIter, edgeEnd) = in_edges(vertex, graph); edgeIter != edgeEnd; ++edgeIter) {
466  auto t = order[source(*edgeIter, graph)];
467 
468  if (maximum < t) {
469  maximum = t;
470  }
471  }
472 
473  order[vertex] = maximum + 1;
474 
475  if (levelCount < order[vertex]) {
476  levelCount = order[vertex];
477  }
478  }
479  }
480 
481  std::vector<std::vector<VertexT>> results(levelCount + 1, std::vector<VertexT>());
482  VertexIterator vertexIter, vertexEnd;
483 
484  for (boost::tie(vertexIter, vertexEnd) = vertices(graph); vertexIter != vertexEnd; ++vertexIter) {
485  const auto position = order[*vertexIter];
486  results[position].emplace_back(graph[*vertexIter]);
487  }
488 
489  return results;
490  }
491 
495  public: bool hasCycles() const {
496  std::vector<std::pair<VertexT, VertexT>> cycleEdges;
497  CycleDetector cycleDetector(cycleEdges);
498  boost::depth_first_search(graph, boost::visitor(cycleDetector));
499  return !cycleEdges.empty();
500  }
501 
507  public: bool hasCycles(std::vector<std::pair<VertexT, VertexT>> & cycleEdges) const {
508  const auto sz = cycleEdges.size();
509  CycleDetector cycleDetector(cycleEdges);
510  boost::depth_first_search(graph, boost::visitor(cycleDetector));
511  return cycleEdges.size() > sz;
512  }
513 
517  public: iterator begin() {
518  VertexIterator begin, end;
519  boost::tie(begin, end) = boost::vertices(graph);
520  return iterator(graph, begin);
521  }
522 
526  public: iterator end() {
527  VertexIterator begin, end;
528  boost::tie(begin, end) = boost::vertices(graph);
529  return iterator(graph, end);
530  }
531 
535  public: const_iterator begin() const {
536  VertexIterator begin, end;
537  boost::tie(begin, end) = boost::vertices(graph);
538  return const_iterator(graph, begin);
539  }
540 
544  public: const_iterator end() const {
545  VertexIterator begin, end;
546  boost::tie(begin, end) = boost::vertices(graph);
547  return const_iterator(graph, end);
548  }
549 
554  public: void logGraph(LoggingLevel level, const char * title) {
555  if (!Impl::ContainerLogger::log().enabled(level)) {
556  return;
557  }
558 
559  std::ostringstream builder;
560 
561  builder << title << "\n";
562 
563  VertexIterator vertexIter, vertexEnd;
564  InEdgeIterator edgeIter, edgeEnd;
565 
567 
568  const int indexLength = (int) toString(reverseLookup.size()).length();
569 
570  for (boost::tie(vertexIter, vertexEnd) = vertices(graph); vertexIter != vertexEnd; ++vertexIter) {
571  auto vertex = *vertexIter;
572  auto value = graph[vertex];
573 
574  builder << std::right << std::setw(indexLength) << vertex << ": " << toString(value) << "\n";
575 
576  std::string_view prefix = " deps: ";
577  for (boost::tie(edgeIter, edgeEnd) = in_edges(vertex, graph); edgeIter != edgeEnd; ++edgeIter) {
578  auto s = source(*edgeIter, graph);
579  builder << prefix << s;
580  prefix = ", ";
581  }
582 
583  if (prefix == ", ") {
584  builder << "\n";
585  }
586  }
587 
588  Impl::ContainerLogger::log().log(level, builder.str().c_str());
589  }
590 
592 
593  private: struct CycleDetector : public boost::dfs_visitor<> {
594  explicit CycleDetector(std::vector<std::pair<VertexT, VertexT>> & cycleEdges_) : cycleEdges(cycleEdges_) {}
595 
596  template <class Edge, class Graph> void back_edge(Edge e, Graph & g) {
597  VertexT s = g[source(e, g)];
598  VertexT t = g[target(e, g)];
599  cycleEdges.emplace_back(std::pair<VertexT, VertexT>(s, t));
600  }
601 
602  private: std::vector<std::pair<VertexT, VertexT>> & cycleEdges;
603  };
604 
605  // This would not be required if the graph library had reverse vertex
606  // property lookup (maybe it has this hidden somewhere).
607  private: void rebuildReverseLookup() {
608  reverseLookup.clear();
609  VertexIterator vertexIter, vertexEnd;
610 
611  for (boost::tie(vertexIter, vertexEnd) = vertices(graph); vertexIter != vertexEnd; ++vertexIter) {
612  auto vertex = *vertexIter;
613  auto value = graph[vertex];
614 
615  reverseLookup.insert(std::pair<VertexT, Vertex>(value, vertex));
616  }
617  }
618 
619  private: Graph graph;
620  private: std::unordered_map<VertexT, Vertex> reverseLookup;
621 };
622 
623 } // namespace Balau::Container
624 
625 #pragma clang diagnostic pop
626 
627 #endif // COM_BORA_SOFTWARE__BALAU_CONTAINER_DEPENDENCY_GRAPH
std::vector< VertexT > dependencyOrder() const
Calculate the dependency order of the dependencies.
Definition: DependencyGraph.hpp:436
iterator & operator=(const iterator &copy)
Set the current iterator to a copy of the supplied iterator.
Definition: DependencyGraph.hpp:109
const_iterator(const const_iterator &copy)
Create an iterator by copying the supplied iterator.
Definition: DependencyGraph.hpp:190
const VertexT * lookupDependency(const VertexT &key) const
Lookups up the dependency that matches the supplied candidate object.
Definition: DependencyGraph.hpp:423
#define ThrowBalauException(ExceptionClass,...)
Throw a Balau style exception, with implicit file and line number, and optional stacktrace.
Definition: BalauException.hpp:45
Thrown when a non-existent relationship between two items is requested.
Definition: ContainerExceptions.hpp:84
const_iterator begin() const
Get a const iterator positioned at the beginning of the dependency graph.
Definition: DependencyGraph.hpp:535
bool hasCycles() const
Does the dependency graph have any cycles?
Definition: DependencyGraph.hpp:495
iterator begin()
Get an iterator positioned at the beginning of the dependency graph.
Definition: DependencyGraph.hpp:517
Various container classes, apart from interprocess containers.
Definition: ArrayBlockingQueue.hpp:25
LoggingLevel
The logging level.
Definition: LoggingLevel.hpp:32
Thrown when an attempt is made to add an existing item to a container.
Definition: ContainerExceptions.hpp:62
std::vector< std::vector< VertexT > > parallelDependencyOrder() const
Calculate the parallel dependency order of the dependencies.
Definition: DependencyGraph.hpp:454
Balau exceptions for containers.
Balau::U8String< AllocatorT > toString(const BalauException &e)
Base class toString<AllocatorT> function for Balau exceptions.
Definition: BalauException.hpp:122
bool hasCycles(std::vector< std::pair< VertexT, VertexT >> &cycleEdges) const
Does the dependency graph have any cycles?
Definition: DependencyGraph.hpp:507
bool hasDependency(const VertexT &dependency) const
Does the graph have the specified dependency?
Definition: DependencyGraph.hpp:398
void logGraph(LoggingLevel level, const char *title)
Log the dependency tree to the "balau.container" logger if the logger is enabled for the supplied log...
Definition: DependencyGraph.hpp:554
iterator(const iterator &copy)
Create an iterator by copying the supplied iterator.
Definition: DependencyGraph.hpp:103
std::vector< VertexT > directDependenciesOf(const VertexT &dependency) const
What are the direct dependencies of the specified dependency.
Definition: DependencyGraph.hpp:405
Thrown when an attempt is made to remove a non-existing item in a container.
Definition: ContainerExceptions.hpp:73
bool operator!=(const iterator &rhs) const
returns true if the current iterator is not equal to the supplied iterator.
Definition: DependencyGraph.hpp:169
void addRelationship(const VertexT &independent, const VertexT &dependent, const EdgeT &edgeData)
Add a dependency relationship between two existing dependencies, with edge data.
Definition: DependencyGraph.hpp:336
void addDependency(const VertexT &dependency, bool throwIfExists=true)
Add a dependency.
Definition: DependencyGraph.hpp:268
iterator end()
Get an iterator positioned at the end of the dependency graph.
Definition: DependencyGraph.hpp:526
void removeRelationship(const VertexT &independent, const VertexT &dependent)
Remove a dependency relationship between two existing dependencies.
Definition: DependencyGraph.hpp:354
VertexT & operator->()
Dereference the iterator in order to obtain the object pointed to.
Definition: DependencyGraph.hpp:148
A graph that models the dependencies between a set of objects of type VertexT.
Definition: DependencyGraph.hpp:88
The DependencyGraph const iterator.
Definition: DependencyGraph.hpp:186
void addRelationship(const VertexT &independent, const VertexT &dependent)
Add a dependency relationship between two existing dependencies.
Definition: DependencyGraph.hpp:318
iterator & operator++()
Increment the iterator.
Definition: DependencyGraph.hpp:126
The DependencyGraph non-const iterator.
Definition: DependencyGraph.hpp:99
VertexT & operator*()
Dereference the iterator in order to obtain the object pointed to.
Definition: DependencyGraph.hpp:134
Balau::U8String< AllocatorT > toString(LoggingLevel level)
Print the logging level as a UTF-8 string.
Definition: LoggingLevel.hpp:73
bool operator==(const iterator &rhs) const
returns true if the current iterator is equal to the supplied iterator.
Definition: DependencyGraph.hpp:162
void removeDependency(const VertexT &dependency, bool throwIfNotExists=true)
Remove a dependency.
Definition: DependencyGraph.hpp:294
const_iterator end() const
Get a const iterator positioned at the end of the dependency graph.
Definition: DependencyGraph.hpp:544