0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
nested_trait.h
Go to the documentation of this file.
1/**
2 * @file src/common/model/nested_trait.h
3 *
4 * Copyright (c) 2021-2025 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18#pragma once
19
20#include "util/util.h"
21
22#include <iostream>
23#include <optional>
24#include <set>
25#include <string>
26#include <vector>
27
29
30/**
31 * @brief Base class for elements nested in the diagram.
32 *
33 * This class provides a common trait for diagram elements which can contain
34 * other nested elements, e.g. packages.
35 *
36 * @embed{nested_trait_hierarchy_class.svg}
37 *
38 * @tparam T Type of element
39 * @tparam Path Type of nested path (e.g. namespace or directory path)
40 */
41template <typename T, typename Path> class nested_trait {
42public:
43 nested_trait() = default;
44
45 nested_trait(const nested_trait &) = delete;
46 nested_trait(nested_trait &&) noexcept = default;
47
48 nested_trait &operator=(const nested_trait &) = delete;
49 nested_trait &operator=(nested_trait &&) noexcept = default;
50
51 virtual ~nested_trait() = default;
52
53 bool is_root() const { return is_root_; }
54
55 void is_root(bool a) { is_root_ = a; }
56
57 /**
58 * Add element at the current nested level.
59 *
60 * @tparam V Type of element
61 * @param p Element
62 * @return True, if element was added.
63 */
64 template <typename V = T>
65 [[nodiscard]] bool add_element(std::unique_ptr<V> p)
66 {
67 if (auto id = p->id();
68 added_elements_.count(std::make_pair<eid_t, std::string>(
69 std::move(id), p->type_name()))) {
70 // Element already in element tree
71 return false;
72 }
73
74 added_elements_.emplace(p->id(), p->type_name());
75 elements_by_name_.emplace(p->name(), elements_.size());
76 elements_.emplace_back(std::move(p));
77
78 return true;
79 }
80
81 /**
82 * Add element at a nested path.
83 *
84 * @tparam V Type of element
85 * @param path Nested path (e.g. list of namespaces)
86 * @param p Element
87 * @return True, if element was added.
88 */
89 template <typename V = T>
90 bool add_element(const Path &path, std::unique_ptr<V> p)
91 {
92 assert(p);
93
94 LOG_TRACE("Adding nested element {} at path '{}{}'", p->name(),
95 path.is_root() ? "::" : "", path.to_string());
96
97 if (path.is_empty()) {
98 return add_element(std::move(p));
99 }
100
101 auto parent = get_element(path);
102
103 if (parent) {
104 auto *nested_trait_ptr =
105 dynamic_cast<nested_trait<T, Path> *>(&parent.value());
106 if (nested_trait_ptr != nullptr) {
107 p->set_parent_element_id(parent.value().id());
108 return (*nested_trait_ptr)
109 .template add_element<V>(std::move(p));
110 }
111 }
112
113 LOG_INFO("No parent element found at: {}", path.to_string());
114
115 throw std::runtime_error(
116 "No parent element found for " + path.to_string());
117 }
118
119 /**
120 * Get element at path, if exists.
121 *
122 * @tparam V Element type.
123 * @param path Path to the element.
124 * @return Optional reference to the element.
125 */
126 template <typename V = T> auto get_element(const Path &path) const
127 {
128 if (path.is_empty() || !has_element(path[0])) {
129 LOG_DBG("Nested element {} not found in element", path.to_string());
130 return optional_ref<V>{};
131 }
132
133 if (path.size() == 1) {
134 return get_element<V>(path[0], path.is_root());
135 }
136
137 auto p = get_element<T>(path[0], path.is_root());
138
139 if (!p)
140 return optional_ref<V>{};
141
142 if (dynamic_cast<nested_trait<T, Path> *>(&p.value()))
143 return dynamic_cast<nested_trait<T, Path> &>(p.value())
144 .get_element<V>(Path{path.begin() + 1, path.end()});
145
146 return optional_ref<V>{};
147 }
148
149 /**
150 * Get element by name at the current nested level.
151 *
152 * @tparam V Type of element.
153 * @param name Name of the element (cannot contain namespace or path)
154 * @return Optional reference to the element.
155 */
156 template <typename V = T>
157 auto get_element(const std::string &name, bool is_root = false) const
158 {
159 assert(!util::contains(name, "::"));
160
161 // Try to find an element by name and assuming it is a specific type
162 // For some reason it is legal to have a C/C++ struct with the same
163 // name as some ObjC protocol/interface, so the name is not
164 // necessarily unique
165
166 auto [it, matches_end] = elements_by_name_.equal_range(name);
167
168 while (true) {
169 if (it == matches_end)
170 break;
171
172 assert((*it).second < elements_.size());
173
174 auto [element_name, element_index] = *it;
175
176 // Return the element if it has the expected type
177 if (auto element_ptr =
178 dynamic_cast<V *>(elements_.at(element_index).get());
179 element_ptr != nullptr) {
180 if (auto nt_ptr = dynamic_cast<nested_trait<T, Path> *>(
181 elements_.at(element_index).get());
182 nt_ptr != nullptr) {
183 if (nt_ptr->is_root_ == is_root)
184 return optional_ref<V>{std::ref<V>(*element_ptr)};
185 }
186 else
187 return optional_ref<V>{std::ref<V>(*element_ptr)};
188 }
189
190 ++it;
191 }
192
193 return optional_ref<V>{};
194 }
195
196 /**
197 * Return result of functor f applied to all_of elements.
198 * @tparam F Functor type
199 * @param f Functor value
200 * @return True, if functor return true for elements, including nested
201 * ones.
202 */
203 template <typename F> bool all_of(F &&f) const
204 {
205 return std::all_of(
206 elements_.cbegin(), elements_.cend(), [f](const auto &e) {
207 const auto *package_ptr =
208 dynamic_cast<nested_trait<T, Path> *>(e.get());
209
210 if (package_ptr != nullptr)
211 return package_ptr->all_of(f);
212
213 return f(*e);
214 });
215 }
216
217 /**
218 * Check if nested element is empty.
219 *
220 * @return True if this nested element is empty.
221 */
222 bool is_empty(bool include_inner_packages = false) const
223 {
224 // If we're interested whether the tree contains any elements including
225 // packages (even if their empty) just check elements_
226 if (include_inner_packages)
227 return elements_.empty();
228
229 // If we're interested only in non-package elements, that we have to
230 // traverse the nested chain
231 return elements_.empty() ||
232 std::all_of(elements_.cbegin(), elements_.cend(), [](auto &e) {
233 const auto *package_ptr =
234 dynamic_cast<nested_trait<T, Path> *>(e.get());
235 return package_ptr != nullptr && package_ptr->is_empty();
236 });
237 }
238
239 auto begin() { return elements_.begin(); }
240 auto end() { return elements_.end(); }
241
242 auto cbegin() const { return elements_.cbegin(); }
243 auto cend() const { return elements_.cend(); }
244
245 auto begin() const { return elements_.begin(); }
246 auto end() const { return elements_.end(); }
247
248 /**
249 * Print the nested trait in the form of a tree.
250 *
251 * This method is used for debugging only.
252 *
253 * @param level Tree level
254 */
255 void print_tree(const int level) const
256 {
257 const auto &d = *this;
258
259 if (level == 0) {
260 std::cout << "--- Printing tree:\n";
261 }
262 for (const auto &e : d) {
263 if (dynamic_cast<nested_trait<T, Path> *>(e.get())) {
264 std::cout << std::string(level, '.') << "[" << *e << "]\n";
265 dynamic_cast<nested_trait<T, Path> *>(e.get())->print_tree(
266 level + 1);
267 }
268 else {
269 std::cout << std::string(level, '.') << "- " << *e << "]\n";
270 }
271 }
272 }
273
274 void remove(const std::set<eid_t> &element_ids)
275 {
276 // Find all elements positions to remove
277 size_t idx{0};
278 for (const auto &e : elements_) {
279 if (element_ids.count(e->id()) > 0) {
280 auto range = elements_by_name_.equal_range(e->name());
281
282 for (auto it = range.first; it != range.second; ++it) {
283 if (it->second == idx) {
284 elements_by_name_.erase(it);
285 break;
286 }
287 }
288 }
289
290 idx++;
291 }
292
293 // First remove all matching elements on this level
294 elements_.erase(std::remove_if(elements_.begin(), elements_.end(),
295 [&element_ids](auto &&e) {
296 return element_ids.count(e->id()) > 0;
297 }),
298 elements_.end());
299
300 decltype(added_elements_) to_keep_;
301
302 for (const auto &e : added_elements_) {
303 if (element_ids.count(e.first) == 0)
304 to_keep_.emplace(e);
305 }
306
307 std::swap(to_keep_, added_elements_);
308
309 // Now recurse to any packages on this level
310 for (auto &p : elements_) {
311 if (dynamic_cast<nested_trait<T, Path> *>(p.get()))
312 dynamic_cast<nested_trait<T, Path> *>(p.get())->remove(
313 element_ids);
314 }
315 }
316
317private:
318 /**
319 * Returns true of this nested level contains an element with specified
320 * name.
321 *
322 * @param name Name of the element.
323 * @return True if element exists.
324 */
325 bool has_element(const std::string &name) const
326 {
327 return elements_by_name_.count(name) != 0U;
328 }
329
330 bool is_root_{false};
331
332 std::set<std::pair<eid_t, std::string>> added_elements_;
333 std::vector<std::unique_ptr<T>> elements_;
334 std::multimap<std::string, size_t> elements_by_name_;
335};
336
337} // namespace clanguml::common::model