Added from_to sequence diagram generator for plantuml
This commit is contained in:
@@ -416,152 +416,19 @@ void generator::generate_diagram(std::ostream &ostr) const
|
|||||||
const auto &from_location = ft.front();
|
const auto &from_location = ft.front();
|
||||||
const auto &to_location = ft.back();
|
const auto &to_location = ft.back();
|
||||||
|
|
||||||
if ((from_location.location_type == location_t::function) &&
|
auto message_chains_unique =
|
||||||
(to_location.location_type == location_t::function)) {
|
model().get_all_from_to_message_chains(from_location, to_location);
|
||||||
common::model::diagram_element::id_t from_activity{0};
|
|
||||||
common::model::diagram_element::id_t to_activity{0};
|
|
||||||
|
|
||||||
for (const auto &[k, v] : model().sequences()) {
|
bool first_separator_skipped{false};
|
||||||
const auto &caller = *model().participants().at(v.from());
|
for (const auto &mc : message_chains_unique) {
|
||||||
std::string vfrom = caller.full_name(false);
|
if (!first_separator_skipped)
|
||||||
if (vfrom == from_location.location) {
|
first_separator_skipped = true;
|
||||||
LOG_DBG("Found sequence diagram start point '{}': {}",
|
else
|
||||||
vfrom, k);
|
ostr << "====\n";
|
||||||
from_activity = k;
|
|
||||||
break;
|
for (const auto &m : mc) {
|
||||||
}
|
generate_call(m, ostr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (from_activity == 0) {
|
|
||||||
LOG_WARN("Failed to find 'from' participant {} for start_from "
|
|
||||||
"condition",
|
|
||||||
from_location.location);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &[k, v] : model().sequences()) {
|
|
||||||
for (const auto &m : v.messages()) {
|
|
||||||
if (m.type() != message_t::kCall)
|
|
||||||
continue;
|
|
||||||
const auto &callee = *model().participants().at(m.to());
|
|
||||||
std::string vto = callee.full_name(false);
|
|
||||||
if (vto == to_location.location) {
|
|
||||||
LOG_DBG("Found sequence diagram end point '{}': {}",
|
|
||||||
vto, m.to());
|
|
||||||
to_activity = m.to();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (to_activity == 0) {
|
|
||||||
LOG_WARN("Failed to find 'to' participant {} for from_to "
|
|
||||||
"condition",
|
|
||||||
to_location.location);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message (call) chains matching the specified from_to condition
|
|
||||||
std::vector<message_chain_t> message_chains;
|
|
||||||
|
|
||||||
// First find all 'to_activity' call targets in the sequences, i.e.
|
|
||||||
// all messages pointing to the final 'to_activity' activity
|
|
||||||
for (const auto &[k, v] : model().sequences()) {
|
|
||||||
for (const auto &m : v.messages()) {
|
|
||||||
if (m.type() != message_t::kCall)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (m.to() == to_activity) {
|
|
||||||
message_chains.push_back(message_chain_t{});
|
|
||||||
message_chains.back().push_back(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<int, model::message> calls_to_current_chain;
|
|
||||||
std::map<int, message_chain_t> current_chain;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
bool added_message_to_some_chain{false};
|
|
||||||
// If target of current message matches any of the
|
|
||||||
// 'from' constraints in the last messages in
|
|
||||||
// current chains - append
|
|
||||||
|
|
||||||
if (!calls_to_current_chain.empty()) {
|
|
||||||
for (auto &[message_chain_index, message] :
|
|
||||||
calls_to_current_chain) {
|
|
||||||
message_chains.push_back(
|
|
||||||
current_chain[message_chain_index]);
|
|
||||||
message_chains.back().push_back(std::move(
|
|
||||||
calls_to_current_chain[message_chain_index]));
|
|
||||||
}
|
|
||||||
calls_to_current_chain.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto i = 0U; i < message_chains.size(); i++) {
|
|
||||||
auto &mc = message_chains[i];
|
|
||||||
current_chain[i] = mc;
|
|
||||||
for (const auto &[k, v] : model().sequences()) {
|
|
||||||
|
|
||||||
for (const auto &m : v.messages()) {
|
|
||||||
if (m.type() != message_t::kCall)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Ignore recursive calls
|
|
||||||
if (m.to() == m.from()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m.to() == mc.back().from()) {
|
|
||||||
calls_to_current_chain[i] = m;
|
|
||||||
added_message_to_some_chain = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are more than one call to the current chain,
|
|
||||||
// duplicate it as many times as there are calls - 1
|
|
||||||
if (calls_to_current_chain.size() >= 1) {
|
|
||||||
mc.push_back(calls_to_current_chain[i]);
|
|
||||||
calls_to_current_chain.erase(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is nothing more to find
|
|
||||||
if (!added_message_to_some_chain)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Reverse the message chains order (they were added starting from
|
|
||||||
// the destination activity)
|
|
||||||
for (auto &mc : message_chains)
|
|
||||||
std::reverse(mc.begin(), mc.end());
|
|
||||||
|
|
||||||
// Remove identical chains
|
|
||||||
std::unordered_set<message_chain_t> message_chains_unique{
|
|
||||||
message_chains.begin(), message_chains.end()};
|
|
||||||
|
|
||||||
auto idx{0U};
|
|
||||||
for (const auto &mc : message_chains_unique) {
|
|
||||||
if (mc.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (mc.front().from() != from_activity)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (const auto &m : mc) {
|
|
||||||
generate_call(m, ostr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx++ < message_chains_unique.size() - 1)
|
|
||||||
ostr << "====\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// TODO: Add support for other sequence start location types
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ template <typename C, typename D>
|
|||||||
using common_generator =
|
using common_generator =
|
||||||
clanguml::common::generators::plantuml::generator<C, D>;
|
clanguml::common::generators::plantuml::generator<C, D>;
|
||||||
|
|
||||||
using message_chain_t =
|
|
||||||
std::vector<sequence_diagram::model::message>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sequence diagram PlantUML generator
|
* @brief Sequence diagram PlantUML generator
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -209,6 +209,161 @@ std::vector<std::string> diagram::list_start_from_values() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unordered_set<message_chain_t> diagram::get_all_from_to_message_chains(
|
||||||
|
const config::source_location &from_location,
|
||||||
|
const config::source_location &to_location) const
|
||||||
|
{
|
||||||
|
std::unordered_set<message_chain_t> message_chains_unique{};
|
||||||
|
|
||||||
|
if ((from_location.location_type == config::location_t::function) &&
|
||||||
|
(to_location.location_type == config::location_t::function)) {
|
||||||
|
common::model::diagram_element::id_t from_activity{0};
|
||||||
|
common::model::diagram_element::id_t to_activity{0};
|
||||||
|
|
||||||
|
for (const auto &[k, v] : sequences()) {
|
||||||
|
const auto &caller = *participants().at(v.from());
|
||||||
|
std::string vfrom = caller.full_name(false);
|
||||||
|
if (vfrom == from_location.location) {
|
||||||
|
LOG_DBG(
|
||||||
|
"Found sequence diagram start point '{}': {}", vfrom, k);
|
||||||
|
from_activity = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from_activity == 0) {
|
||||||
|
LOG_WARN("Failed to find 'from' participant {} for start_from "
|
||||||
|
"condition",
|
||||||
|
from_location.location);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &[k, v] : sequences()) {
|
||||||
|
for (const auto &m : v.messages()) {
|
||||||
|
if (m.type() != common::model::message_t::kCall)
|
||||||
|
continue;
|
||||||
|
const auto &callee = *participants().at(m.to());
|
||||||
|
std::string vto = callee.full_name(false);
|
||||||
|
if (vto == to_location.location) {
|
||||||
|
LOG_DBG("Found sequence diagram end point '{}': {}", vto,
|
||||||
|
m.to());
|
||||||
|
to_activity = m.to();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to_activity == 0) {
|
||||||
|
LOG_WARN("Failed to find 'to' participant {} for from_to "
|
||||||
|
"condition",
|
||||||
|
to_location.location);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message (call) chains matching the specified from_to condition
|
||||||
|
std::vector<message_chain_t> message_chains;
|
||||||
|
|
||||||
|
// First find all 'to_activity' call targets in the sequences, i.e.
|
||||||
|
// all messages pointing to the final 'to_activity' activity
|
||||||
|
for (const auto &[k, v] : sequences()) {
|
||||||
|
for (const auto &m : v.messages()) {
|
||||||
|
if (m.type() != common::model::message_t::kCall)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (m.to() == to_activity) {
|
||||||
|
message_chains.push_back(message_chain_t{});
|
||||||
|
message_chains.back().push_back(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<int, std::vector<model::message>> calls_to_current_chain;
|
||||||
|
std::map<int, message_chain_t> current_chain;
|
||||||
|
|
||||||
|
int iter = 0;
|
||||||
|
while (true) {
|
||||||
|
bool added_message_to_some_chain{false};
|
||||||
|
// If target of current message matches any of the
|
||||||
|
// 'from' constraints in the last messages in
|
||||||
|
// current chains found on previous iteration - append
|
||||||
|
if (!calls_to_current_chain.empty()) {
|
||||||
|
for (auto &[message_chain_index, messages] :
|
||||||
|
calls_to_current_chain) {
|
||||||
|
for (auto i = 0U; i < messages.size(); i++) {
|
||||||
|
message_chains.push_back(
|
||||||
|
current_chain[message_chain_index]);
|
||||||
|
|
||||||
|
message_chains.back().push_back(std::move(messages[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calls_to_current_chain.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_TRACE("Message chains after iteration {}", iter++);
|
||||||
|
int message_chain_index{};
|
||||||
|
for (const auto &mc : message_chains) {
|
||||||
|
LOG_TRACE("\t{}: {}", message_chain_index++,
|
||||||
|
fmt::join(util::map<std::string>(mc,
|
||||||
|
[](const model::message &m) -> std::string {
|
||||||
|
return m.message_name();
|
||||||
|
}),
|
||||||
|
"<-"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = 0U; i < message_chains.size(); i++) {
|
||||||
|
auto &mc = message_chains[i];
|
||||||
|
current_chain[i] = mc;
|
||||||
|
for (const auto &[k, v] : sequences()) {
|
||||||
|
for (const auto &m : v.messages()) {
|
||||||
|
if (m.type() != common::model::message_t::kCall)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Ignore recursive calls and call loops
|
||||||
|
if (m.to() == m.from() ||
|
||||||
|
std::any_of(
|
||||||
|
cbegin(mc), cend(mc), [&m](const auto &msg) {
|
||||||
|
return msg.to() == m.from();
|
||||||
|
})) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.to() == mc.back().from()) {
|
||||||
|
calls_to_current_chain[i].push_back(m);
|
||||||
|
added_message_to_some_chain = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are more than one call to the current chain,
|
||||||
|
// duplicate it as many times as there are calls - 1
|
||||||
|
if (calls_to_current_chain.count(i) > 0 &&
|
||||||
|
calls_to_current_chain[i].size() >= 1) {
|
||||||
|
mc.push_back(calls_to_current_chain[i][0]);
|
||||||
|
calls_to_current_chain[i].erase(
|
||||||
|
calls_to_current_chain[i].begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is nothing more to find
|
||||||
|
if (!added_message_to_some_chain)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the message chains order (they were added starting from
|
||||||
|
// the destination activity)
|
||||||
|
for (auto &mc : message_chains)
|
||||||
|
std::reverse(mc.begin(), mc.end());
|
||||||
|
|
||||||
|
std::copy_if(message_chains.begin(), message_chains.end(),
|
||||||
|
std::inserter(message_chains_unique, message_chains_unique.begin()),
|
||||||
|
[from_activity](const message_chain_t &mc) {
|
||||||
|
return !mc.empty() && (mc.front().from() == from_activity);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return message_chains_unique;
|
||||||
|
}
|
||||||
|
|
||||||
void diagram::print() const
|
void diagram::print() const
|
||||||
{
|
{
|
||||||
LOG_TRACE(" --- Participants ---");
|
LOG_TRACE(" --- Participants ---");
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "activity.h"
|
#include "activity.h"
|
||||||
#include "common/model/diagram.h"
|
#include "common/model/diagram.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
#include "config/config.h"
|
||||||
#include "participant.h"
|
#include "participant.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -27,6 +28,8 @@
|
|||||||
|
|
||||||
namespace clanguml::sequence_diagram::model {
|
namespace clanguml::sequence_diagram::model {
|
||||||
|
|
||||||
|
using message_chain_t = std::vector<sequence_diagram::model::message>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Model of a sequence diagram
|
* @brief Model of a sequence diagram
|
||||||
*
|
*
|
||||||
@@ -235,6 +238,15 @@ public:
|
|||||||
*/
|
*/
|
||||||
std::vector<std::string> list_start_from_values() const;
|
std::vector<std::string> list_start_from_values() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate a list of message chains matching a from_to constraint
|
||||||
|
*
|
||||||
|
* @return List of message chains
|
||||||
|
*/
|
||||||
|
std::unordered_set<message_chain_t> get_all_from_to_message_chains(
|
||||||
|
const config::source_location &from,
|
||||||
|
const config::source_location &to) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Once the diagram is complete, run any final processing.
|
* @brief Once the diagram is complete, run any final processing.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -351,6 +351,15 @@ void for_each_if(const T &collection, C &&cond, F &&func)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename R, typename T, typename F>
|
||||||
|
std::vector<R> map(const std::vector<T> &in, F &&f)
|
||||||
|
{
|
||||||
|
std::vector<R> out;
|
||||||
|
std::transform(
|
||||||
|
in.cbegin(), in.cend(), std::back_inserter(out), std::forward<F>(f));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, typename F, typename FElse>
|
template <typename T, typename F, typename FElse>
|
||||||
void if_not_null(const T *pointer, F &&func, FElse &&func_else)
|
void if_not_null(const T *pointer, F &&func, FElse &&func_else)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ struct A {
|
|||||||
void a3() { }
|
void a3() { }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct C;
|
||||||
|
|
||||||
struct B {
|
struct B {
|
||||||
void b1()
|
void b1()
|
||||||
{
|
{
|
||||||
@@ -15,8 +17,11 @@ struct B {
|
|||||||
}
|
}
|
||||||
void b2() { a.a2(); }
|
void b2() { a.a2(); }
|
||||||
void b3() { a.a3(); }
|
void b3() { a.a3(); }
|
||||||
|
void b4();
|
||||||
|
|
||||||
A a;
|
A a;
|
||||||
|
|
||||||
|
C *c;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct C {
|
struct C {
|
||||||
@@ -30,6 +35,8 @@ struct C {
|
|||||||
c2();
|
c2();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void c4() { b.b4(); }
|
||||||
|
|
||||||
B b;
|
B b;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,6 +50,8 @@ struct D {
|
|||||||
c.c3();
|
c.c3();
|
||||||
a.a2();
|
a.a2();
|
||||||
|
|
||||||
|
c.c4();
|
||||||
|
|
||||||
auto l = [this]() { a.a2(); };
|
auto l = [this]() { a.a2(); };
|
||||||
l();
|
l();
|
||||||
}
|
}
|
||||||
@@ -51,5 +60,11 @@ struct D {
|
|||||||
A a;
|
A a;
|
||||||
C c;
|
C c;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void B::b4()
|
||||||
|
{
|
||||||
|
c->c4();
|
||||||
|
b2();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,21 +43,20 @@ TEST_CASE("t20034", "[test-case][sequence]")
|
|||||||
REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b2()"));
|
REQUIRE_THAT(puml, HasCall(_A("C"), _A("B"), "b2()"));
|
||||||
REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()"));
|
REQUIRE_THAT(puml, HasCall(_A("B"), _A("A"), "a2()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, HasCall(_A("D"), _A("C"), "c4()"));
|
||||||
|
|
||||||
|
REQUIRE_THAT(puml, !HasCall(_A("C"), _A("B"), "b3()"));
|
||||||
REQUIRE_THAT(puml, HasCall(_A("D"), _A("C"), "c2()"));
|
|
||||||
REQUIRE_THAT(puml, !HasCall(_A("D"), _A("C"), "c1()"));
|
|
||||||
|
|
||||||
|
|
||||||
save_puml(
|
save_puml(
|
||||||
config.output_directory() + "/" + diagram->name + ".puml", puml);
|
config.output_directory() + "/" + diagram->name + ".puml", puml);
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
// {
|
||||||
// auto j = generate_sequence_json(diagram, *model);
|
// auto j = generate_sequence_json(diagram, *model);
|
||||||
//
|
//
|
||||||
// using namespace json;
|
// using namespace json;
|
||||||
//
|
//
|
||||||
// save_json(config.output_directory() + "/" + diagram->name + ".json", j);
|
// save_json(config.output_directory() + "/" + diagram->name +
|
||||||
// }
|
// ".json", j);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user