File indexing completed on 2024-05-19 05:42:06

0001 // ct_lvtldr_graphloader.cpp                                          -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtldr_graphloader.h>
0021 
0022 #include <ct_lvtldr_lakosiannode.h>
0023 
0024 #include <ct_lvtshr_stringhelpers.h>
0025 
0026 #include <cassert>
0027 #include <iostream>
0028 #include <unordered_map>
0029 #include <unordered_set>
0030 
0031 // TODO: Have a way to enable debugs from application
0032 //       While removing the dependency from ldr to QtGui and QtWidgets, we needed to avoid touching the Preferences
0033 //       module directly, since they depend on those things. We need a way to enable/disable debug messages from this
0034 //       module without touching the Preferences global directly to avoid unwanted dependencies.
0035 //       Old code for reference:
0036 //       Preferences::self()->debug()->enableDebugOutput() || Preferences::self()->debug()->storeDebugOutput();
0037 //
0038 static const bool showDebug = false;
0039 
0040 namespace Codethink::lvtldr {
0041 
0042 // ==========================
0043 // class IGraphLoader
0044 // ==========================
0045 
0046 struct GraphLoader::LoaderVertex {
0047     LakosianNode *node;
0048 
0049     LakosianNode *parent = nullptr;
0050     // If this is a nullptr then we don't load the parent, otherwise use the
0051     // parent pointed to here. This will normally be node->parent(), but
0052     // for some manual layout interventions it could be
0053     // node->parent()->parent(), etc
0054 
0055     bool expanded = false;
0056 
0057     lvtshr::LoaderInfo info;
0058 
0059     LoaderVertex(LakosianNode *node, bool withParent, lvtshr::LoaderInfo info): node(node), info(info)
0060     {
0061         assert(node);
0062         if (withParent) {
0063             parent = node->parent();
0064         } else {
0065             parent = nullptr;
0066         }
0067     }
0068 
0069     bool operator==(const LoaderVertex& other) const
0070     // see VertexHash::operator()
0071     {
0072         return node == other.node;
0073     }
0074 };
0075 
0076 struct GraphLoader::VertexHash {
0077     std::size_t operator()(const LoaderVertex& vertex) const
0078     {
0079         // One vertex per LakosianNode. The other fields are just metadata
0080         return std::hash<LakosianNode *>{}(vertex.node);
0081     }
0082 };
0083 
0084 struct GraphLoader::LoaderEdge {
0085     LakosianNode *source;
0086     LakosianNode *target;
0087     lvtshr::LakosRelationType type;
0088 
0089     LoaderEdge(LakosianNode *source, LakosianNode *target, lvtshr::LakosRelationType type):
0090         source(source), target(target), type(type)
0091     {
0092     }
0093 
0094     bool operator==(const LoaderEdge& other) const
0095     {
0096         return source == other.source && target == other.target && type == other.type;
0097     }
0098 };
0099 
0100 struct GraphLoader::EdgeHash {
0101     std::size_t operator()(const LoaderEdge& edge) const
0102     {
0103         auto h = std::hash<decltype(edge.source)>{}(edge.source);
0104         auto h2 = std::hash<decltype(edge.target)>{}(edge.target);
0105         h = h ^ (h2 << 1);
0106         auto h3 = std::hash<decltype(edge.type)>{}(edge.type);
0107         h = h ^ (h3 << 1);
0108         return h;
0109     }
0110 };
0111 
0112 struct GraphLoader::Private {
0113     IGraphLoader *graph = nullptr;
0114     NodeStorage *store = nullptr;
0115 
0116     std::unordered_set<LoaderVertex, VertexHash> vertices;
0117     std::unordered_set<LoaderEdge, EdgeHash> edges;
0118     std::unordered_map<LakosianNode *, lvtqtc::LakosEntity *> entityMap;
0119 };
0120 
0121 GraphLoader::GraphLoader(): d(std::make_unique<GraphLoader::Private>())
0122 {
0123 }
0124 
0125 GraphLoader::~GraphLoader() noexcept = default;
0126 
0127 void GraphLoader::unload(LakosianNode *node)
0128 {
0129     auto mapIt = d->entityMap.find(node);
0130     if (mapIt != std::end(d->entityMap)) {
0131         d->entityMap.erase(mapIt);
0132     }
0133 
0134     auto verticeIt = std::find_if(std::begin(d->vertices), std::end(d->vertices), [node](const LoaderVertex& v) {
0135         return v.node == node;
0136     });
0137     if (verticeIt != std::end(d->vertices)) {
0138         d->vertices.erase(verticeIt);
0139     }
0140 
0141     // C++17 lacks a std::remove_if for maps and sets
0142     std::unordered_set<LoaderEdge, EdgeHash> temp_edges;
0143     for (const auto& edge : d->edges) {
0144         if (edge.source == node || edge.target == node) {
0145             continue;
0146         }
0147         temp_edges.insert(edge);
0148     }
0149     d->edges = temp_edges;
0150 }
0151 
0152 void GraphLoader::setGraph(IGraphLoader *graph)
0153 {
0154     d->graph = graph;
0155 }
0156 
0157 lvtqtc::LakosEntity *GraphLoader::load(LakosianNode *node)
0158 // loop up the LoaderVertex for node then call load() on that
0159 {
0160     assert(node);
0161 
0162     auto [it, _] = d->vertices.emplace(LoaderVertex(node, false, lvtshr::LoaderInfo(false, false, false)));
0163     return load(*it);
0164 }
0165 
0166 lvtqtc::LakosEntity *GraphLoader::load(const GraphLoader::LoaderVertex& vertex)
0167 {
0168     if (d->entityMap.count(vertex.node)) {
0169         return d->entityMap[vertex.node];
0170     }
0171 
0172     lvtqtc::LakosEntity *parent = nullptr;
0173 
0174     // make sure our parents are added first
0175     if (vertex.parent) {
0176         parent = load(vertex.parent);
0177     }
0178 
0179     qDebug() << "lvtldr::IGraphLoader: adding vertex " << vertex.node->qualifiedName();
0180 
0181     if (parent) {
0182         qDebug() << " parent: " << vertex.node->parent()->qualifiedName();
0183     }
0184     qDebug() << " expanded: " << vertex.expanded;
0185 
0186     lvtqtc::LakosEntity *entity = nullptr;
0187     if (d->graph) {
0188         switch (vertex.node->type()) {
0189         case lvtshr::DiagramType::ClassType:
0190             entity = d->graph->addUdtVertex(vertex.node, vertex.expanded, parent, vertex.info);
0191             break;
0192         case lvtshr::DiagramType::PackageType:
0193             entity = d->graph->addPkgVertex(vertex.node, vertex.expanded, parent, vertex.info);
0194             break;
0195         case lvtshr::DiagramType::RepositoryType:
0196             entity = d->graph->addRepositoryVertex(vertex.node, vertex.expanded, parent, vertex.info);
0197             break;
0198         case lvtshr::DiagramType::ComponentType:
0199             entity = d->graph->addCompVertex(vertex.node, vertex.expanded, parent, vertex.info);
0200             break;
0201         case lvtshr::DiagramType::FreeFunctionType:
0202             entity = d->graph->addUdtVertex(vertex.node, vertex.expanded, parent, vertex.info);
0203             break;
0204         case lvtshr::DiagramType::NoneType:
0205             break;
0206         }
0207     }
0208 
0209     d->entityMap.insert({vertex.node, entity});
0210     return entity;
0211 }
0212 
0213 void GraphLoader::load(const LoaderEdge& edge)
0214 {
0215     if (!d->graph) {
0216         return;
0217     }
0218 
0219     lvtqtc::LakosEntity *source = load(edge.source);
0220     lvtqtc::LakosEntity *target = load(edge.target);
0221     assert(source && "Missing source pointer");
0222     assert(target && "Missing target pointer");
0223 
0224     switch (edge.type) {
0225     case lvtshr::IsA:
0226         d->graph->addIsARelation(source, target);
0227         break;
0228     case lvtshr::PackageDependency:
0229         d->graph->addPackageDependencyRelation(source, target);
0230         break;
0231     case lvtshr::UsesInTheImplementation:
0232         d->graph->addUsesInTheImplementationRelation(source, target);
0233         break;
0234     case lvtshr::UsesInTheInterface:
0235         d->graph->addUsesInTheInterfaceRelation(source, target);
0236         break;
0237     case lvtshr::UsesInNameOnly:
0238     case lvtshr::None:
0239         assert(false && "Unhandled edge type");
0240     }
0241 }
0242 
0243 void GraphLoader::clear()
0244 {
0245     if (showDebug) {
0246         qDebug() << "Clearing Inner Loader";
0247     }
0248     d->vertices.clear();
0249     d->edges.clear();
0250     d->entityMap.clear();
0251 }
0252 
0253 void GraphLoader::addVertex(LakosianNode *node, bool withParent, lvtshr::LoaderInfo info)
0254 {
0255     assert(node);
0256 
0257     auto [it, inserted] = d->vertices.emplace(node, withParent, info);
0258     if (inserted && showDebug) {
0259         qDebug() << "GraphLoader: Added node " << QString::fromStdString(node->qualifiedName());
0260     }
0261 
0262     if (!inserted && withParent) {
0263         // const_cast is safe here because this does not effect the hash
0264         const_cast<LoaderVertex&>(*it).info = info;
0265         if (!it->parent) {
0266             const_cast<LoaderVertex&>(*it).parent = node->parent();
0267             const_cast<LoaderVertex&>(*it).info.setHasParent(true);
0268         }
0269     }
0270 
0271     if (it->parent) {
0272         // make sure parent knows it needs to be a subgraph
0273         auto [parentIt, _] = d->vertices.emplace(node->parent(), false, lvtshr::LoaderInfo(false, false, false));
0274         // const_cast is safe here because expanded does not effect the hash value
0275         const_cast<LoaderVertex&>(*parentIt).expanded = true;
0276         // don't set hasChildren in the LoaderInfo because we might not have
0277         // *all* of the children
0278     }
0279 }
0280 
0281 void GraphLoader::addEdge(LakosianNode *source, const LakosianEdge& edge, bool reverse)
0282 {
0283     LakosianNode *dest = edge.other();
0284     if (reverse) {
0285         std::swap(source, dest);
0286     }
0287 
0288     d->edges.emplace(source, dest, edge.type());
0289 }
0290 
0291 void GraphLoader::loadForwardEdges()
0292 {
0293     for (const LoaderVertex& vertex : d->vertices) {
0294         for (const LakosianEdge& edge : vertex.node->providers()) {
0295             // if we are drawing the other side of the edge anyway, we should
0296             // draw the edge
0297             if (d->vertices.count({edge.other(), false, {}})) {
0298                 addEdge(vertex.node, edge, false);
0299             }
0300         }
0301     }
0302 }
0303 
0304 void GraphLoader::loadReverseEdges()
0305 {
0306     for (const LoaderVertex& vertex : d->vertices) {
0307         for (const LakosianEdge& edge : vertex.node->clients()) {
0308             // if we are drawing the other side of the edge anyway, we should
0309             // draw the edge
0310             if (d->vertices.count({edge.other(), false, {}})) {
0311                 addEdge(vertex.node, edge, true);
0312             }
0313         }
0314     }
0315 }
0316 
0317 void GraphLoader::load()
0318 {
0319     // set parent for any vertices which have their parent loaded but don't
0320     // know about it
0321     for (const LoaderVertex& vertex : d->vertices) {
0322         if (vertex.parent) {
0323             // parent already set
0324             continue;
0325         }
0326 
0327         LakosianNode *parent = vertex.node->parent();
0328         while (parent) {
0329             // if the node's parent has already been added, set withParent
0330             auto parentIt = d->vertices.find(LoaderVertex(parent, false, {}));
0331             if (parentIt != d->vertices.end()) {
0332                 // const_cast safe because it doesn't change the hash value
0333                 const_cast<LoaderVertex&>(vertex).parent = parent;
0334                 const_cast<LoaderVertex&>(*parentIt).expanded = true;
0335                 break;
0336             }
0337 
0338             // try grandparent's etc
0339             parent = parent->parent();
0340         }
0341     }
0342 
0343     for (const LoaderVertex& vertex : d->vertices) {
0344         // the vertex will add its own parents if these were not already added
0345         load(vertex);
0346     }
0347 
0348     for (const LoaderEdge& edge : d->edges) {
0349         load(edge);
0350     }
0351 }
0352 
0353 } // namespace Codethink::lvtldr