0.5.4
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
diagram.h
Go to the documentation of this file.
1/**
2 * @file src/class_diagram/model/diagram.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 "class.h"
25#include "common/types.h"
26#include "concept.h"
27#include "config/config.h"
28#include "enum.h"
29
30#include <regex>
31#include <string>
32#include <unordered_set>
33#include <vector>
34
36
37using common::opt_ref;
38using common::model::diagram_element;
40using common::model::element_view;
41using common::model::path;
43
47
48/**
49 * @brief Class representing a class diagram.
50 */
52 public element_view<class_>,
53 public element_view<enum_>,
54 public element_view<concept_>,
55 public nested_trait_ns {
56public:
57 diagram() = default;
58
59 diagram(const diagram &) = delete;
60 diagram(diagram &&) = default;
61 diagram &operator=(const diagram &) = delete;
62 diagram &operator=(diagram &&) = default;
63
64 /**
65 * @brief Get the diagram model type - in this case class.
66 *
67 * @return Type of class diagram.
68 */
69 diagram_t type() const override;
70
71 /**
72 * Inherit the should_include methods from the common diagram model.
73 */
75
76 /**
77 * @brief Whether a class_member should be included in the diagram.
78 *
79 * @param m Class member
80 * @return True, if class member should be included in the diagram.
81 */
82 bool should_include(const class_member &m) const;
83
84 /**
85 * @brief Whether a class_method should be included in the diagram.
86 *
87 * @param m Class method
88 * @return True, if class method should be included in the diagram.
89 */
90 bool should_include(const class_method &m) const;
91
92 /**
93 * @brief Search for element in the diagram by fully qualified name.
94 *
95 * @param full_name Fully qualified element name.
96 * @return Optional reference to a diagram element.
97 */
98 opt_ref<diagram_element> get(const std::string &full_name) const override;
99
100 /**
101 * @brief Search for element in the diagram by id.
102 *
103 * @param id Element id.
104 * @return Optional reference to a diagram element.
105 */
106 opt_ref<diagram_element> get(eid_t id) const override;
107
108 /**
109 * @brief Get list of references to classes in the diagram model.
110 *
111 * @return List of references to classes in the diagram model.
112 */
114
115 /**
116 * @brief Get list of references to enums in the diagram model.
117 *
118 * @return List of references to enums in the diagram model.
119 */
121
122 /**
123 * @brief Get list of references to concepts in the diagram model.
124 *
125 * @return List of references to concepts in the diagram model.
126 */
128
129 /**
130 * @brief Check, if diagram contains a specific element.
131 *
132 * @tparam ElementT Type of diagram element (e.g. class_)
133 * @param e Element to check
134 * @return True, if element already exists in the diagram
135 */
136 template <typename ElementT> bool contains(const ElementT &e);
137
138 /**
139 * @brief Find an element in the diagram by name.
140 *
141 * This method allows for typed search, where the type of searched for
142 * element is determined from template specialization.
143 *
144 * @tparam ElementT Type of element (e.g. class_)
145 * @param name Fully qualified name of the element
146 * @return Optional reference to a diagram element
147 */
148 template <typename ElementT>
149 opt_ref<ElementT> find(const std::string &name) const;
150
151 /**
152 * @brief Find elements in the diagram by regex pattern.
153 *
154 * This method allows for typed search, where the type of searched for
155 * element is determined from template specialization.
156 *
157 * @tparam ElementT Type of element (e.g. class_)
158 * @param name String or regex pattern
159 * @return List of optional references to matched elements.
160 */
161 template <typename ElementT>
162 std::vector<opt_ref<ElementT>> find(
163 const clanguml::common::string_or_regex &pattern) const;
164
165 /**
166 * @brief Find an element in the diagram by id.
167 *
168 * This method allows for typed search, where the type of searched for
169 * element is determined from template specialization.
170 *
171 * @tparam ElementT Type of element (e.g. class_)
172 * @param id Id of the element
173 * @return Optional reference to a diagram element
174 */
175 template <typename ElementT> opt_ref<ElementT> find(eid_t id) const;
176
177 /**
178 * @brief Get reference to vector of elements of specific type
179 *
180 * @tparam ElementT Type of elements view
181 * @return Reference to elements vector
182 */
183 template <typename ElementT>
185
186 /**
187 * @brief Add element to the diagram at a specified nested path.
188 *
189 * Adds an element to a diagram, at a specific package (if any exist).
190 * The package is specified by the `parent_path`, which can be either
191 * a namespace or a directory path.
192 *
193 * @tparam ElementT Type of diagram element.
194 * @param parent_path Path to the parent package of the new diagram element.
195 * @param e Diagram element to be added.
196 * @return True, if the element was added to the diagram.
197 */
198 template <typename ElementT>
199 bool add(const path &parent_path, std::unique_ptr<ElementT> &&e)
200 {
201 if (parent_path.type() == common::model::path_type::kNamespace) {
202 return add_with_namespace_path(std::move(e));
203 }
204
205 if (parent_path.type() == common::model::path_type::kModule) {
206 return add_with_module_path(parent_path, std::move(e));
207 }
208
209 return add_with_filesystem_path(parent_path, std::move(e));
210 }
211
212 /**
213 * @brief Convert element id to PlantUML alias.
214 *
215 * @todo This method does not belong here - refactor to PlantUML specific
216 * code.
217 *
218 * @param id Id of the diagram element.
219 * @return PlantUML alias.
220 */
221 std::string to_alias(eid_t id) const;
222
223 /**
224 * @brief Given an initial set of classes, add all their parents to the
225 * argument.
226 * @param parents In and out parameter with the parent classes.
227 */
229
230 /**
231 * @brief Check if diagram contains element by id.
232 *
233 * @todo Remove in favour of 'contains'
234 *
235 * @param id Id of the element.
236 * @return True, if diagram contains an element with a specific id.
237 */
238 bool has_element(eid_t id) const override;
239
240 /**
241 * @brief Remove redundant dependency relationships
242 */
244
245 /**
246 * @brief Return the elements JSON context for inja templates.
247 *
248 * @return JSON node with elements context.
249 */
250 inja::json context() const override;
251
252 /**
253 * @brief Check whether the diagram is empty
254 *
255 * @return True, if diagram is empty
256 */
257 bool is_empty() const override;
258
259 void apply_filter() override;
260
261private:
262 template <typename ElementT>
263 bool add_with_namespace_path(std::unique_ptr<ElementT> &&e);
264
265 template <typename ElementT>
267 const common::model::path &parent_path, std::unique_ptr<ElementT> &&e);
268
269 template <typename ElementT>
271 const common::model::path &parent_path, std::unique_ptr<ElementT> &&e);
272};
273
274template <typename ElementT> bool diagram::contains(const ElementT &element)
275{
276 return std::any_of(element_view<ElementT>::view().cbegin(),
278 [&element](
279 const auto &element_opt) { return element_opt.get() == element; });
280}
281
282template <typename ElementT>
283bool diagram::add_with_namespace_path(std::unique_ptr<ElementT> &&e)
284{
285 const auto base_name = e->name();
286 const auto full_name = e->full_name(false);
287 const auto element_type = e->type_name();
288
289 LOG_DBG("Adding {}: {}::{}, {}", element_type,
290 e->get_namespace().to_string(), base_name, full_name);
291
292 if (util::contains(base_name, "::"))
293 throw std::runtime_error("Name cannot contain namespace: " + base_name);
294
295 if (util::contains(base_name, "*"))
296 throw std::runtime_error("Name cannot contain *: " + base_name);
297
298 const auto ns = e->get_relative_namespace();
299 auto name = base_name;
300 auto name_with_ns = e->name_and_ns();
301 auto name_and_ns = ns | name;
302 auto &e_ref = *e;
303 auto id = e_ref.id();
304
305 try {
306 if (!contains(e_ref)) {
307 if (add_element(ns, std::move(e)))
308 element_view<ElementT>::add(std::ref(e_ref));
309
310 const auto &el = get_element<ElementT>(name_and_ns).value();
311
312 if ((el.name() != name) || !(el.get_relative_namespace() == ns))
313 throw std::runtime_error(
314 "Invalid element stored in the diagram tree");
315
316 LOG_DBG("Added {} {} ({} - [{}])", element_type, base_name,
317 full_name, id);
318
319 return true;
320 }
321 }
322 catch (const std::runtime_error &e) {
323 LOG_WARN("Cannot add {} {} with id {} due to: {}", element_type, name,
324 id, e.what());
325 return false;
326 }
327
328 LOG_DBG("{} {} ({} - [{}]) already in the model", element_type, base_name,
329 full_name, id);
330
331 return false;
332}
333
334template <typename ElementT>
336 const common::model::path &parent_path, std::unique_ptr<ElementT> &&e)
337{
338 const auto element_type = e->type_name();
339
340 // Make sure all parent modules are already packages in the
341 // model
342 for (auto it = parent_path.begin(); it != parent_path.end(); it++) {
343 auto pkg = std::make_unique<common::model::package>(
344 e->using_namespace(), parent_path.type());
345 pkg->set_name(*it);
346 auto ns =
347 common::model::path(parent_path.begin(), it, parent_path.type());
348 // ns.pop_back();
349 pkg->set_namespace(ns);
350 pkg->set_id(common::to_id(pkg->full_name(false)));
351
352 add(ns, std::move(pkg));
353 }
354
355 const auto base_name = e->name();
356 const auto full_name = e->full_name(false);
357 auto &e_ref = *e;
358
359 if (add_element(parent_path, std::move(e))) {
360 element_view<ElementT>::add(std::ref(e_ref));
361 return true;
362 }
363
364 return false;
365}
366
367template <typename ElementT>
369 const common::model::path &parent_path, std::unique_ptr<ElementT> &&e)
370{
371 const auto element_type = e->type_name();
372
373 // Make sure all parent modules are already packages in the
374 // model
375 for (auto it = parent_path.begin(); it != parent_path.end(); it++) {
376 auto pkg = std::make_unique<common::model::package>(
377 e->using_namespace(), parent_path.type());
378 pkg->set_name(*it);
379 auto ns =
380 common::model::path(parent_path.begin(), it, parent_path.type());
381 // ns.pop_back();
382 pkg->set_namespace(ns);
383 pkg->set_id(common::to_id(pkg->full_name(false)));
384
385 add(ns, std::move(pkg));
386 }
387
388 const auto base_name = e->name();
389 const auto full_name = e->full_name(false);
390 auto &e_ref = *e;
391
392 if (add_element(parent_path, std::move(e))) {
393 element_view<ElementT>::add(std::ref(e_ref));
394 return true;
395 }
396
397 return false;
398}
399
400template <typename ElementT>
401opt_ref<ElementT> diagram::find(const std::string &name) const
402{
403 for (const auto &element : element_view<ElementT>::view()) {
404 const auto full_name = element.get().full_name(false);
405
406 auto full_name_escaped = full_name;
407 util::replace_all(full_name_escaped, "##", "::");
408
409 if (name == full_name || name == full_name_escaped) {
410 return {element};
411 }
412 }
413
414 return {};
415}
416
417template <typename ElementT>
418std::vector<opt_ref<ElementT>> diagram::find(
419 const common::string_or_regex &pattern) const
420{
421 std::vector<opt_ref<ElementT>> result;
422
423 for (const auto &element : element_view<ElementT>::view()) {
424 const auto full_name = element.get().full_name(false);
425 auto full_name_escaped = full_name;
426 util::replace_all(full_name_escaped, "##", "::");
427
428 if (pattern == full_name || pattern == full_name_escaped) {
429 result.emplace_back(element);
430 }
431 }
432
433 return result;
434}
435
436template <typename ElementT> opt_ref<ElementT> diagram::find(eid_t id) const
437{
438 for (const auto &element : element_view<ElementT>::view()) {
439 if (element.get().id() == id) {
440 return {element};
441 }
442 }
443
444 return {};
445}
446
447template <typename ElementT>
449{
451}
452
453//
454// Template method specialization pre-declarations...
455//
456template <>
457bool diagram::add_with_namespace_path<common::model::package>(
458 std::unique_ptr<common::model::package> &&p);
459
460template <>
461bool diagram::add_with_module_path<common::model::package>(
462 const common::model::path &parent_path,
463 std::unique_ptr<common::model::package> &&p);
464
465template <>
466bool diagram::add_with_filesystem_path<common::model::package>(
467 const common::model::path &parent_path,
468 std::unique_ptr<common::model::package> &&p);
469
470} // namespace clanguml::class_diagram::model
471
472namespace clanguml::common::model {
473template <>
474bool check_diagram_type<clanguml::class_diagram::model::diagram>(diagram_t t);
475}