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