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