File indexing completed on 2024-05-19 05:42:08
0001 // ct_lvtldr_packagenode.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_packagenode.h> 0021 0022 #include <ct_lvtldr_nodestorage.h> 0023 #include <ct_lvtshr_stringhelpers.h> 0024 #include <regex> 0025 0026 namespace { 0027 using namespace Codethink; 0028 0029 std::string fixQName(const std::string& qname) 0030 { 0031 return std::regex_replace(qname, std::regex("\\\\"), "/"); 0032 } 0033 0034 } // namespace 0035 0036 namespace Codethink::lvtldr { 0037 0038 using namespace Codethink::lvtshr; 0039 0040 // ========================== 0041 // class PackageNode 0042 // ========================== 0043 0044 PackageNode::PackageNode(NodeStorage& store, 0045 std::optional<std::reference_wrapper<DatabaseHandler>> dbHandler, 0046 std::optional<PackageNodeFields> fields): 0047 LakosianNode(store, dbHandler), d_dbHandler(dbHandler), d_fields(*fields) 0048 { 0049 d_qualifiedNameParts = NamingUtils::buildQualifiedNamePrefixParts(fixQName(d_fields.qualifiedName), "/"); 0050 setName(d_fields.name); 0051 } 0052 0053 PackageNode::~PackageNode() noexcept = default; 0054 0055 lvtshr::DiagramType PackageNode::type() const 0056 { 0057 return lvtshr::DiagramType::PackageType; 0058 } 0059 0060 bool PackageNode::isPackageGroup() 0061 { 0062 // TODO: Properly handle caching system 0063 d->childrenLoaded = false; 0064 loadChildren(); 0065 return !d->innerPackages.empty(); 0066 } 0067 0068 std::string PackageNode::dirPath() const 0069 { 0070 return d_fields.diskPath; 0071 } 0072 0073 bool PackageNode::hasRepository() const 0074 { 0075 return d_fields.sourceRepositoryId.has_value(); 0076 } 0077 0078 bool PackageNode::isStandalone() const 0079 { 0080 if (d_qualifiedNameParts.empty()) { 0081 return false; 0082 } 0083 return d_qualifiedNameParts[0] == "standalones"; 0084 } 0085 0086 void PackageNode::removeChildPackage(PackageNode *child) 0087 { 0088 auto& v = d_fields.childPackagesIds; 0089 v.erase(std::remove(v.begin(), v.end(), child->id()), v.end()); 0090 } 0091 0092 void PackageNode::addConcreteDependency(PackageNode *other) 0093 { 0094 d_fields.providerIds.emplace_back(other->id()); 0095 other->d_fields.clientIds.emplace_back(d_fields.id); 0096 d_dbHandler->get().addConcreteDependency(d_fields.id, other->id()); 0097 } 0098 0099 void PackageNode::removeConcreteDependency(PackageNode *other) 0100 { 0101 { 0102 auto& v = d_fields.providerIds; 0103 v.erase(std::remove(v.begin(), v.end(), other->id()), v.end()); 0104 } 0105 { 0106 auto& v = other->d_fields.clientIds; 0107 v.erase(std::remove(v.begin(), v.end(), other->id()), v.end()); 0108 } 0109 d_dbHandler->get().removeConcreteDependency(d_fields.id, other->id()); 0110 } 0111 0112 bool PackageNode::hasConcreteDependency(LakosianNode *other) 0113 { 0114 auto& v = d_fields.providerIds; 0115 return std::find(v.begin(), v.end(), other->id()) != v.end(); 0116 } 0117 0118 std::string PackageNode::canonicalName() const 0119 { 0120 // A '+' in a package name often indicates externally driven naming exceptions. 0121 // Example: bsl+bslhdrs 0122 auto canonicalName = name(); 0123 if (canonicalName.find('+') == 3) { 0124 canonicalName = canonicalName.substr(0, 3); 0125 } 0126 return canonicalName; 0127 } 0128 0129 namespace { 0130 // return true if this element is inside of the searchNode's children, recursive. 0131 bool existsInTree(LakosianNode *entity, LakosianNode *searchNode) 0132 { 0133 if (entity == searchNode) { 0134 return true; 0135 } 0136 0137 auto const& children = searchNode->children(); 0138 return std::any_of(children.cbegin(), children.cend(), [&entity](LakosianNode *child) { 0139 return existsInTree(entity, child); 0140 }); 0141 } 0142 } // namespace 0143 0144 cpp::result<void, AddChildError> PackageNode::addChild(LakosianNode *child) 0145 { 0146 // don't allow packages with circular dependencies 0147 auto childChildren = child->children(); 0148 if (existsInTree(this, child)) { 0149 const std::string errorString = "The entity" + name() + "is already connected to" + child->name() 0150 + "as a child, it can't be set as a parent"; 0151 return cpp::fail(AddChildError{errorString}); 0152 } 0153 0154 // Don't add the same child twice 0155 if (std::find(std::begin(d->children), std::end(d->children), child) != std::end(d->children)) { 0156 const std::string errorString = "The entity" + child->name() + "is already a child of" + name(); 0157 return cpp::fail(AddChildError{errorString}); 0158 } 0159 0160 d->children.push_back(child); 0161 if (dynamic_cast<PackageNode *>(child) != nullptr) { 0162 d->innerPackages.push_back(child); 0163 d_fields.childPackagesIds.push_back(child->id()); 0164 } else { 0165 d_fields.childComponentsIds.push_back(child->id()); 0166 } 0167 auto& dbHandler = d_dbHandler->get(); 0168 dbHandler.updateFields(d_fields); 0169 Q_EMIT onChildCountChanged(d->children.size()); 0170 return {}; 0171 } 0172 0173 void PackageNode::removeChild(LakosianNode *child) 0174 { 0175 { 0176 auto& v = d->children; 0177 v.erase(std::remove_if(v.begin(), 0178 v.end(), 0179 [&child](auto&& e) { 0180 return e->id() == child->id(); 0181 }), 0182 v.end()); 0183 } 0184 0185 if (dynamic_cast<PackageNode *>(child) != nullptr) { 0186 { 0187 auto& v = d->innerPackages; 0188 v.erase(std::remove_if(v.begin(), 0189 v.end(), 0190 [&child](auto&& e) { 0191 return e->id() == child->id(); 0192 }), 0193 v.end()); 0194 } 0195 { 0196 auto& v = d_fields.childPackagesIds; 0197 v.erase(std::remove(v.begin(), v.end(), child->id()), v.end()); 0198 } 0199 } else { 0200 auto& v = d_fields.childComponentsIds; 0201 v.erase(std::remove(v.begin(), v.end(), child->id()), v.end()); 0202 } 0203 d_dbHandler->get().updateFields(d_fields); 0204 Q_EMIT onChildCountChanged(d->children.size()); 0205 } 0206 0207 void PackageNode::setName(std::string const& newName) 0208 { 0209 LakosianNode::setName(newName); 0210 d_fields.name = name(); 0211 d_fields.qualifiedName = qualifiedName(); 0212 d_dbHandler->get().updateFields(d_fields); 0213 } 0214 0215 std::string PackageNode::qualifiedName() const 0216 { 0217 return NamingUtils::buildQualifiedName(d_qualifiedNameParts, name(), "/"); 0218 } 0219 0220 std::string PackageNode::parentName() 0221 { 0222 auto *parentEntity = parent(); 0223 if (!parentEntity) { 0224 return ""; 0225 } 0226 return parentEntity->name(); 0227 } 0228 0229 long long PackageNode::id() const 0230 { 0231 return d_fields.id; 0232 } 0233 0234 lvtshr::UniqueId PackageNode::uid() const 0235 { 0236 return {lvtshr::DiagramType::PackageType, id()}; 0237 } 0238 0239 LakosianNode::IsLakosianResult PackageNode::isLakosian() 0240 { 0241 // Package and package group naming rules are available at 0242 // https://github.com/bloomberg/bde/wiki/Physical-Code-Organization 0243 auto *parentPkg = parent(); 0244 0245 // Standalone package naming rules 0246 if (!parentPkg && QString::fromStdString(name()).startsWith("s_")) { 0247 auto activeName = name(); 0248 // 2 for s_ + 3-to-6 for the actual package name 0249 if (activeName.size() < 2 + 3 || activeName.size() > 2 + 6) { 0250 return IsLakosianResult::PackageNameInvalidNumberOfChars; 0251 } 0252 return IsLakosianResult::IsLakosian; 0253 } 0254 0255 // Package naming rules 0256 if (parentPkg) { 0257 if (!parentPkg->isPackageGroup()) { 0258 return IsLakosianResult::PackageParentIsNotGroup; 0259 } 0260 0261 // A '+' in a package name often indicates externally driven naming exceptions. 0262 // Example: bsl+bslhdrs 0263 auto activeName = name(); 0264 if (activeName.find('+') == 3) { 0265 activeName = activeName.substr(0, 3); 0266 if (activeName != parentPkg->name()) { 0267 return IsLakosianResult::PackagePrefixDiffersFromGroup; 0268 } 0269 } 0270 0271 if (!lvtshr::StrUtil::beginsWith(activeName, parentPkg->name())) { 0272 return IsLakosianResult::PackagePrefixDiffersFromGroup; 0273 } 0274 0275 if (activeName.size() < 3 || activeName.size() > 6) { 0276 return IsLakosianResult::PackageNameInvalidNumberOfChars; 0277 } 0278 return IsLakosianResult::IsLakosian; 0279 } 0280 0281 // Package Group naming rules 0282 if (name().size() != 3) { 0283 return IsLakosianResult::PackageGroupNameInvalidNumberOfChars; 0284 } 0285 return IsLakosianResult::IsLakosian; 0286 } 0287 0288 void PackageNode::loadParent() 0289 { 0290 if (d->parentLoaded) { 0291 return; 0292 } 0293 d->parentLoaded = true; 0294 0295 d->parent = nullptr; 0296 if (d_fields.sourceRepositoryId) { 0297 d->parent = d->store.findById({DiagramType::RepositoryType, *d_fields.sourceRepositoryId}); 0298 } else if (d_fields.parentId) { 0299 d->parent = d->store.findById({DiagramType::PackageType, *d_fields.parentId}); 0300 } 0301 } 0302 0303 void PackageNode::loadChildren() 0304 { 0305 if (d->childrenLoaded) { 0306 return; 0307 } 0308 d_fields = d_dbHandler->get().getPackageFieldsById(d_fields.id); 0309 d->childrenLoaded = true; 0310 0311 auto pkgChildrenIds = d_fields.childPackagesIds; 0312 auto compChildrenIds = d_fields.childComponentsIds; 0313 d->children.clear(); 0314 d->innerPackages.clear(); 0315 d->children.reserve(pkgChildrenIds.size() + compChildrenIds.size()); 0316 d->innerPackages.reserve(pkgChildrenIds.size()); 0317 0318 for (auto& id : pkgChildrenIds) { 0319 LakosianNode *node = d->store.findById({DiagramType::PackageType, id}); 0320 assert(node); 0321 d->children.push_back(node); 0322 d->innerPackages.push_back(node); 0323 } 0324 0325 for (auto& id : compChildrenIds) { 0326 LakosianNode *node = d->store.findById({DiagramType::ComponentType, id}); 0327 assert(node); 0328 d->children.push_back(node); 0329 } 0330 } 0331 0332 void PackageNode::loadProviders() 0333 { 0334 if (d->providersLoaded) { 0335 return; 0336 } 0337 d_fields = d_dbHandler->get().getPackageFieldsById(d_fields.id); 0338 d->providersLoaded = true; 0339 0340 if (d_fields.parentId) { 0341 // package 0342 for (auto&& id : d_fields.providerIds) { 0343 LakosianNode *node = d->store.findById({DiagramType::PackageType, id}); 0344 d->providers.emplace_back(LakosianEdge{lvtshr::PackageDependency, node}); 0345 } 0346 } else { 0347 // package group 0348 for (auto&& id : d_fields.providerIds) { 0349 LakosianNode *node = d->store.findById({DiagramType::PackageType, id}); 0350 d->providers.emplace_back(LakosianEdge{lvtshr::PackageDependency, node}); 0351 } 0352 0353 for (auto&& childId : d_fields.childPackagesIds) { 0354 auto *child = d->store.findById({DiagramType::PackageType, childId}); 0355 for (auto&& edge : child->providers()) { 0356 auto *providerPkgGroup = edge.other()->parent(); 0357 if (!providerPkgGroup) { 0358 continue; 0359 } 0360 if (providerPkgGroup->id() == id()) { 0361 continue; 0362 } 0363 d->providers.emplace_back(LakosianEdge{lvtshr::PackageDependency, providerPkgGroup}); 0364 } 0365 } 0366 } 0367 } 0368 0369 void PackageNode::loadClients() 0370 { 0371 if (d->clientsLoaded) { 0372 return; 0373 } 0374 d_fields = d_dbHandler->get().getPackageFieldsById(d_fields.id); 0375 d->clientsLoaded = true; 0376 0377 if (d_fields.parentId) { 0378 // package 0379 for (auto&& id : d_fields.clientIds) { 0380 LakosianNode *node = d->store.findById({DiagramType::PackageType, id}); 0381 d->clients.emplace_back(LakosianEdge{lvtshr::PackageDependency, node}); 0382 } 0383 } else { 0384 // package group 0385 for (auto&& id : d_fields.clientIds) { 0386 LakosianNode *node = d->store.findById({DiagramType::PackageType, id}); 0387 d->clients.emplace_back(LakosianEdge{lvtshr::PackageDependency, node}); 0388 } 0389 0390 for (auto&& childId : d_fields.childPackagesIds) { 0391 auto *child = d->store.findById({DiagramType::PackageType, childId}); 0392 for (auto&& edge : child->clients()) { 0393 auto *clientPkgGroup = edge.other()->parent(); 0394 if (!clientPkgGroup) { 0395 continue; 0396 } 0397 if (clientPkgGroup->id() == id()) { 0398 continue; 0399 } 0400 d->clients.emplace_back(LakosianEdge{lvtshr::PackageDependency, clientPkgGroup}); 0401 } 0402 } 0403 } 0404 } 0405 0406 } // namespace Codethink::lvtldr