0.5.4
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-2024 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 /**
54 * Add element at the current nested level.
55 *
56 * @tparam V Type of element
57 * @param p Element
58 * @return True, if element was added.
59 */
60 template <typename V = T>
61 [[nodiscard]] bool add_element(std::unique_ptr<V> p)
62 {
63 auto it = std::find_if(elements_.begin(), elements_.end(),
64 [&p](const auto &e) { return *e == *p; });
65
66 if (it != elements_.end()) {
67 // Element already in element tree
68 return false;
69 }
70
71 elements_.emplace_back(std::move(p));
72
73 return true;
74 }
75
76 /**
77 * Add element at a nested path.
78 *
79 * @tparam V Type of element
80 * @param path Nested path (e.g. list of namespaces)
81 * @param p Element
82 * @return True, if element was added.
83 */
84 template <typename V = T>
85 bool add_element(const Path &path, std::unique_ptr<V> p)
86 {
87 assert(p);
88
89 LOG_DBG("Adding nested element {} at path '{}'", p->name(),
90 path.to_string());
91
92 if (path.is_empty()) {
93 return add_element(std::move(p));
94 }
95
96 auto parent = get_element(path);
97
98 if (parent && dynamic_cast<nested_trait<T, Path> *>(&parent.value())) {
99 p->set_parent_element_id(parent.value().id());
100 return dynamic_cast<nested_trait<T, Path> &>(parent.value())
101 .template add_element<V>(std::move(p));
102 }
103
104 LOG_INFO("No parent element found at: {}", path.to_string());
105
106 throw std::runtime_error(
107 "No parent element found for " + path.to_string());
108 }
109
110 /**
111 * Get element at path, if exists.
112 *
113 * @tparam V Element type.
114 * @param path Path to the element.
115 * @return Optional reference to the element.
116 */
117 template <typename V = T> auto get_element(const Path &path) const
118 {
119 if (path.is_empty() || !has_element(path[0])) {
120 LOG_DBG("Nested element {} not found in element", path.to_string());
121 return optional_ref<V>{};
122 }
123
124 if (path.size() == 1) {
125 return get_element<V>(path[0]);
126 }
127
128 auto p = get_element<T>(path[0]);
129
130 if (!p)
131 return optional_ref<V>{};
132
133 if (dynamic_cast<nested_trait<T, Path> *>(&p.value()))
134 return dynamic_cast<nested_trait<T, Path> &>(p.value())
135 .get_element<V>(Path{path.begin() + 1, path.end()});
136
137 return optional_ref<V>{};
138 }
139
140 /**
141 * Get element by name at the current nested level.
142 *
143 * @tparam V Type of element.
144 * @param name Name of the element (cannot contain namespace or path)
145 * @return Optional reference to the element.
146 */
147 template <typename V = T> auto get_element(const std::string &name) const
148 {
149 assert(!util::contains(name, "::"));
150
151 auto it = std::find_if(elements_.cbegin(), elements_.cend(),
152 [&](const auto &p) { return name == p->name(); });
153
154 if (it == elements_.end())
155 return optional_ref<V>{};
156
157 assert(it->get() != nullptr);
158
159 if (dynamic_cast<V *>(it->get()))
160 return optional_ref<V>{std::ref<V>(dynamic_cast<V &>(*it->get()))};
161
162 return optional_ref<V>{};
163 }
164
165 /**
166 * Returns true of this nested level contains an element with specified
167 * name.
168 *
169 * @param name Name of the element.
170 * @return True if element exists.
171 */
172 bool has_element(const std::string &name) const
173 {
174 return std::find_if(elements_.cbegin(), elements_.cend(),
175 [&](const auto &p) { return name == p->name(); }) !=
176 elements_.end();
177 }
178
179 /**
180 * Return result of functor f applied to all_of elements.
181 * @tparam F Functor type
182 * @param f Functor value
183 * @return True, if functor return true for elements, including nested ones.
184 */
185 template <typename F> bool all_of(F &&f) const
186 {
187 return std::all_of(
188 elements_.cbegin(), elements_.cend(), [f](const auto &e) {
189 const auto *package_ptr =
190 dynamic_cast<nested_trait<T, Path> *>(e.get());
191
192 if (package_ptr != nullptr)
193 return package_ptr->all_of(f);
194
195 return f(*e);
196 });
197 }
198
199 /**
200 * Check if nested element is empty.
201 *
202 * @return True if this nested element is empty.
203 */
204 bool is_empty() const
205 {
206 return elements_.empty() ||
207 std::all_of(elements_.cbegin(), elements_.cend(), [](auto &e) {
208 const auto *package_ptr =
209 dynamic_cast<nested_trait<T, Path> *>(e.get());
210 return package_ptr != nullptr && package_ptr->is_empty();
211 });
212 }
213
214 auto begin() { return elements_.begin(); }
215 auto end() { return elements_.end(); }
216
217 auto cbegin() const { return elements_.cbegin(); }
218 auto cend() const { return elements_.cend(); }
219
220 auto begin() const { return elements_.begin(); }
221 auto end() const { return elements_.end(); }
222
223 /**
224 * Print the nested trait in the form of a tree.
225 *
226 * This method is used for debugging only.
227 *
228 * @param level Tree level
229 */
230 void print_tree(const int level)
231 {
232 const auto &d = *this;
233
234 if (level == 0) {
235 std::cout << "--- Printing tree:\n";
236 }
237 for (const auto &e : d) {
238 if (dynamic_cast<nested_trait<T, Path> *>(e.get())) {
239 std::cout << std::string(level, ' ') << "[" << *e << "]\n";
240 dynamic_cast<nested_trait<T, Path> *>(e.get())->print_tree(
241 level + 1);
242 }
243 else {
244 std::cout << std::string(level, ' ') << "- " << *e << "]\n";
245 }
246 }
247 }
248
249 void remove(const std::set<eid_t> &element_ids)
250 {
251 // First remove all matching elements on this level
252 elements_.erase(std::remove_if(elements_.begin(), elements_.end(),
253 [&element_ids](auto &&e) {
254 return element_ids.count(e->id()) > 0;
255 }),
256 elements_.end());
257
258 // Now recurse to any packages on this level
259 for (auto &p : elements_) {
260 if (dynamic_cast<nested_trait<T, Path> *>(p.get()))
261 dynamic_cast<nested_trait<T, Path> *>(p.get())->remove(
262 element_ids);
263 }
264 }
265
266private:
267 std::vector<std::unique_ptr<T>> elements_;
268};
269
270} // namespace clanguml::common::model