From bd19fe5bfbac6498557411d75f1142dd0749c07b Mon Sep 17 00:00:00 2001 From: Bartek Kryza Date: Sun, 27 Aug 2023 19:03:25 +0200 Subject: [PATCH] Added from_to sequence diagram generator for json --- .../json/sequence_diagram_generator.cc | 46 +++ .../plantuml/sequence_diagram_generator.cc | 10 +- src/sequence_diagram/model/diagram.cc | 290 +++++++++--------- src/sequence_diagram/model/diagram.h | 18 +- tests/t20034/test_case.h | 15 +- 5 files changed, 224 insertions(+), 155 deletions(-) diff --git a/src/sequence_diagram/generators/json/sequence_diagram_generator.cc b/src/sequence_diagram/generators/json/sequence_diagram_generator.cc index 5e5d75f0..73b6278a 100644 --- a/src/sequence_diagram/generators/json/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/json/sequence_diagram_generator.cc @@ -609,6 +609,52 @@ void generator::generate_diagram(nlohmann::json &parent) const } } + for (const auto &ft : config().from_to()) { + // First, find the sequence of activities from 'from' location + // to 'to' location + assert(ft.size() == 2); + + const auto &from_location = ft.front(); + const auto &to_location = ft.back(); + + auto [from_activity_id, to_activity_id] = + model().get_from_to_activity_ids(from_location, to_location); + + if (from_activity_id == 0 || to_activity_id == 0) + continue; + + auto message_chains_unique = model().get_all_from_to_message_chains( + from_activity_id, to_activity_id); + + nlohmann::json sequence; + sequence["from_to"]["from"]["location"] = from_location.location; + sequence["from_to"]["from"]["id"] = from_activity_id; + sequence["from_to"]["to"]["location"] = to_location.location; + sequence["from_to"]["to"]["id"] = to_activity_id; + + block_statements_stack_.push_back(std::ref(sequence)); + + sequence["message_chains"] = nlohmann::json::array(); + + for (const auto &mc : message_chains_unique) { + nlohmann::json message_chain; + + block_statements_stack_.push_back(std::ref(message_chain)); + + for (const auto &m : mc) { + generate_call(m, current_block_statement()); + } + + block_statements_stack_.pop_back(); + + sequence["message_chains"].push_back(std::move(message_chain)); + } + + block_statements_stack_.pop_back(); + + json_["sequences"].push_back(std::move(sequence)); + } + for (const auto &sf : config().start_from()) { if (sf.location_type == location_t::function) { common::model::diagram_element::id_t start_from{0}; diff --git a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc index f60438b4..80b29049 100644 --- a/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc +++ b/src/sequence_diagram/generators/plantuml/sequence_diagram_generator.cc @@ -416,8 +416,14 @@ void generator::generate_diagram(std::ostream &ostr) const const auto &from_location = ft.front(); const auto &to_location = ft.back(); - auto message_chains_unique = - model().get_all_from_to_message_chains(from_location, to_location); + auto [from_activity_id, to_activity_id] = + model().get_from_to_activity_ids(from_location, to_location); + + if (from_activity_id == 0 || to_activity_id == 0) + continue; + + auto message_chains_unique = model().get_all_from_to_message_chains( + from_activity_id, to_activity_id); bool first_separator_skipped{false}; for (const auto &mc : message_chains_unique) { diff --git a/src/sequence_diagram/model/diagram.cc b/src/sequence_diagram/model/diagram.cc index c31cbe24..3909e708 100644 --- a/src/sequence_diagram/model/diagram.cc +++ b/src/sequence_diagram/model/diagram.cc @@ -209,158 +209,162 @@ std::vector diagram::list_start_from_values() const return result; } -std::unordered_set diagram::get_all_from_to_message_chains( - const config::source_location &from_location, +std::pair +diagram::get_from_to_activity_ids(const config::source_location &from_location, const config::source_location &to_location) const +{ + 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 {from_activity, to_activity}; + } + + 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 {from_activity, to_activity}; + } + + return {from_activity, to_activity}; +} + +std::unordered_set diagram::get_all_from_to_message_chains( + const common::model::diagram_element::id_t from_activity, + const common::model::diagram_element::id_t to_activity) const { std::unordered_set 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}; + // Message (call) chains matching the specified from_to condition + std::vector message_chains; - 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; + // 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); } } - - 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_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> calls_to_current_chain; - std::map 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(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); - }); } + std::map> calls_to_current_chain; + std::map 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(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; } diff --git a/src/sequence_diagram/model/diagram.h b/src/sequence_diagram/model/diagram.h index a99c656d..03204652 100644 --- a/src/sequence_diagram/model/diagram.h +++ b/src/sequence_diagram/model/diagram.h @@ -241,11 +241,25 @@ public: /** * @brief Generate a list of message chains matching a from_to constraint * + * @param from_activity Source activity for from_to message chain + * @param to_activity Target activity for from_to message chain * @return List of message chains */ std::unordered_set get_all_from_to_message_chains( - const config::source_location &from, - const config::source_location &to) const; + const common::model::diagram_element::id_t from_activity, + const common::model::diagram_element::id_t to_activity) const; + + /** + * @brief Get ids of from and to activities in from_to constraint + * + * @param from_activity Source activity for from_to message chain + * @param to_activity Target activity for from_to message chain + * @return Pair of activity ids (0 if not found) + */ + std::pair + get_from_to_activity_ids(const config::source_location &from_activity, + const config::source_location &to_activity) const; /** * @brief Once the diagram is complete, run any final processing. diff --git a/tests/t20034/test_case.h b/tests/t20034/test_case.h index bf7d5e54..56c1a957 100644 --- a/tests/t20034/test_case.h +++ b/tests/t20034/test_case.h @@ -51,12 +51,11 @@ TEST_CASE("t20034", "[test-case][sequence]") config.output_directory() + "/" + diagram->name + ".puml", puml); } - // { - // auto j = generate_sequence_json(diagram, *model); - // - // using namespace json; - // - // save_json(config.output_directory() + "/" + diagram->name + - // ".json", j); - // } + { + auto j = generate_sequence_json(diagram, *model); + + using namespace json; + + save_json(config.output_directory() + "/" + diagram->name + ".json", j); + } } \ No newline at end of file