// Copyright (C) 2016 Jochen Becher // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "componentviewcontroller.h" #include "modelutilities.h" #include "packageviewcontroller.h" #include "pxnodeutilities.h" #include "qmt/controller/namecontroller.h" #include "qmt/model_controller/mchildrenvisitor.h" #include "qmt/model_controller/modelcontroller.h" #include "qmt/model/mcomponent.h" #include "qmt/model/mdependency.h" #include "qmt/model/mpackage.h" #include "qmt/tasks/diagramscenecontroller.h" #include #include #include #include #include #include #include // TODO implement removing include dependencies that are not longer used // TODO refactor add/remove relations between ancestor packages into extra controller class using namespace Utils; namespace ModelEditor { namespace Internal { class FindComponentFromFilePath : public qmt::MChildrenVisitor { public: void setFilePath(const QString &filePath); qmt::MComponent *component() const { return m_bestComponent; } void visitMComponent(qmt::MComponent *component) final; private: QString m_elementName; QStringList m_elementsPath; int m_maxPathLength = 0; qmt::MComponent *m_bestComponent = nullptr; }; void FindComponentFromFilePath::setFilePath(const QString &filePath) { m_elementName = qmt::NameController::convertFileNameToElementName(filePath); QFileInfo fileInfo(filePath); m_elementsPath = qmt::NameController::buildElementsPath(fileInfo.path(), false); } void FindComponentFromFilePath::visitMComponent(qmt::MComponent *component) { if (component->name() == m_elementName) { QStringList elementPath; const qmt::MObject *ancestor = component->owner(); while (ancestor) { elementPath.prepend(ancestor->name()); ancestor = ancestor->owner(); } int i = elementPath.size() - 1; int j = m_elementsPath.size() - 1; while (i >= 0 && j >= 0 && elementPath.at(i) == m_elementsPath.at(j)) { --i; --j; } int pathLength = elementPath.size() - 1 - i; if (pathLength > m_maxPathLength) { m_maxPathLength = pathLength; m_bestComponent = component; } } visitMObject(component); } class UpdateIncludeDependenciesVisitor : public qmt::MChildrenVisitor { class Node { public: Node() = default; Node(const QString &filePath, const QStringList &elementPath) : m_filePath(filePath), m_elementPath(elementPath) { } QString m_filePath; QStringList m_elementPath; }; public: void setPackageViewController(PackageViewController *packageViewController); void setModelController(qmt::ModelController *modelController); void setModelUtilities(ModelUtilities *modelUtilities); void updateFilePaths(); void visitMComponent(qmt::MComponent *component) final; private: QStringList findFilePathOfComponent(const qmt::MComponent *component); void collectElementPaths(const ProjectExplorer::FolderNode *folderNode, QMultiHash *filePathsMap); qmt::MComponent *findComponentFromFilePath(const QString &filePath); private: PackageViewController *m_packageViewController = nullptr; qmt::ModelController *m_modelController = nullptr; ModelUtilities *m_modelUtilities = nullptr; QMultiHash m_filePaths; QHash m_filePathComponentsMap; }; void UpdateIncludeDependenciesVisitor::setPackageViewController(PackageViewController *packageViewController) { m_packageViewController = packageViewController; } void UpdateIncludeDependenciesVisitor::setModelController(qmt::ModelController *modelController) { m_modelController = modelController; } void UpdateIncludeDependenciesVisitor::setModelUtilities(ModelUtilities *modelUtilities) { m_modelUtilities = modelUtilities; } void UpdateIncludeDependenciesVisitor::updateFilePaths() { m_filePaths.clear(); for (const ProjectExplorer::Project *project : ProjectExplorer::SessionManager::projects()) { ProjectExplorer::ProjectNode *projectNode = project->rootProjectNode(); if (projectNode) collectElementPaths(projectNode, &m_filePaths); } } void UpdateIncludeDependenciesVisitor::visitMComponent(qmt::MComponent *component) { CppEditor::CppModelManager *cppModelManager = CppEditor::CppModelManager::instance(); CPlusPlus::Snapshot snapshot = cppModelManager->snapshot(); const QStringList filePaths = findFilePathOfComponent(component); for (const QString &filePath : filePaths) { CPlusPlus::Document::Ptr document = snapshot.document(FilePath::fromString(filePath)); if (document) { const QList includes = document->resolvedIncludes(); for (const CPlusPlus::Document::Include &include : includes) { Utils::FilePath includeFilePath = include.resolvedFileName(); // replace proxy header with real one CPlusPlus::Document::Ptr includeDocument = snapshot.document(includeFilePath); if (includeDocument) { QList includes = includeDocument->resolvedIncludes(); if (includes.count() == 1 && includes.at(0).resolvedFileName().fileName() == includeFilePath.fileName()) { includeFilePath = includes.at(0).resolvedFileName(); } } qmt::MComponent *includeComponent = findComponentFromFilePath(includeFilePath.toString()); if (includeComponent && includeComponent != component) { // add dependency between components if (!m_modelUtilities->haveDependency(component, includeComponent)) { auto dependency = new qmt::MDependency; dependency->setFlags(qmt::MElement::ReverseEngineered); dependency->setStereotypes({"include"}); dependency->setDirection(qmt::MDependency::AToB); dependency->setSource(component->uid()); dependency->setTarget(includeComponent->uid()); m_modelController->addRelation(component, dependency); } m_packageViewController->createAncestorDependencies(component, includeComponent); } } } } visitMObject(component); } QStringList UpdateIncludeDependenciesVisitor::findFilePathOfComponent(const qmt::MComponent *component) { QStringList elementPath; const qmt::MObject *ancestor = component->owner(); while (ancestor) { elementPath.prepend(ancestor->name()); ancestor = ancestor->owner(); } QStringList bestFilePaths; int maxPathLength = 1; const QList nodes = m_filePaths.values(component->name()); for (const Node &node : nodes) { int i = elementPath.size() - 1; int j = node.m_elementPath.size() - 1; while (i >= 0 && j >= 0 && elementPath.at(i) == node.m_elementPath.at(j)) { --i; --j; } int pathLength = elementPath.size() - 1 - i; if (pathLength > maxPathLength) bestFilePaths.clear(); if (pathLength >= maxPathLength) { maxPathLength = pathLength; bestFilePaths.append(node.m_filePath); } } return bestFilePaths; } void UpdateIncludeDependenciesVisitor::collectElementPaths(const ProjectExplorer::FolderNode *folderNode, QMultiHash *filePathsMap) { const QList fileNodes = folderNode->fileNodes(); for (const ProjectExplorer::FileNode *fileNode : fileNodes) { QString elementName = qmt::NameController::convertFileNameToElementName(fileNode->filePath().toString()); QFileInfo fileInfo = fileNode->filePath().toFileInfo(); QString nodePath = fileInfo.path(); QStringList elementsPath = qmt::NameController::buildElementsPath(nodePath, false); filePathsMap->insert(elementName, Node(fileNode->filePath().toString(), elementsPath)); } const QList subNodes = folderNode->folderNodes(); for (const ProjectExplorer::FolderNode *subNode : subNodes) collectElementPaths(subNode, filePathsMap); } qmt::MComponent *UpdateIncludeDependenciesVisitor::findComponentFromFilePath(const QString &filePath) { const auto it = m_filePathComponentsMap.constFind(filePath); if (it != m_filePathComponentsMap.cend()) return it.value(); FindComponentFromFilePath visitor; visitor.setFilePath(filePath); m_modelController->rootPackage()->accept(&visitor); qmt::MComponent *component = visitor.component(); m_filePathComponentsMap.insert(filePath, component); return component; } class ComponentViewController::ComponentViewControllerPrivate { public: ModelUtilities *modelUtilities = nullptr; PackageViewController *packageViewController = nullptr; PxNodeUtilities *pxnodeUtilities = nullptr; qmt::DiagramSceneController *diagramSceneController = nullptr; }; ComponentViewController::ComponentViewController(QObject *parent) : QObject(parent), d(new ComponentViewControllerPrivate) { } ComponentViewController::~ComponentViewController() { delete d; } void ComponentViewController::setModelUtilities(ModelUtilities *modelUtilities) { d->modelUtilities = modelUtilities; } void ComponentViewController::setPackageViewController(PackageViewController *packageViewController) { d->packageViewController = packageViewController; } void ComponentViewController::setPxNodeUtilties(PxNodeUtilities *pxnodeUtilities) { d->pxnodeUtilities = pxnodeUtilities; } void ComponentViewController::setDiagramSceneController(qmt::DiagramSceneController *diagramSceneController) { d->diagramSceneController = diagramSceneController; } void ComponentViewController::createComponentModel(const QString &filePath, qmt::MDiagram *diagram, const QString &anchorFolder) { d->diagramSceneController->modelController()->startResetModel(); doCreateComponentModel(filePath, diagram, anchorFolder, false); doCreateComponentModel(filePath, diagram, anchorFolder, true); d->diagramSceneController->modelController()->finishResetModel(true); } void ComponentViewController::updateIncludeDependencies(qmt::MPackage *rootPackage) { d->diagramSceneController->modelController()->startResetModel(); UpdateIncludeDependenciesVisitor visitor; visitor.setPackageViewController(d->packageViewController); visitor.setModelController(d->diagramSceneController->modelController()); visitor.setModelUtilities(d->modelUtilities); visitor.updateFilePaths(); rootPackage->accept(&visitor); d->diagramSceneController->modelController()->finishResetModel(true); } void ComponentViewController::doCreateComponentModel(const QString &filePath, qmt::MDiagram *diagram, const QString &anchorFolder, bool scanHeaders) { for (const QString &fileName : QDir(filePath).entryList(QDir::Files)) { QString file = filePath + "/" + fileName; QString componentName = qmt::NameController::convertFileNameToElementName(file); qmt::MComponent *component = nullptr; bool isSource = false; CppEditor::ProjectFile::Kind kind = CppEditor::ProjectFile::classify(file); switch (kind) { case CppEditor::ProjectFile::CSource: case CppEditor::ProjectFile::CXXSource: case CppEditor::ProjectFile::ObjCSource: case CppEditor::ProjectFile::ObjCXXSource: case CppEditor::ProjectFile::CudaSource: case CppEditor::ProjectFile::OpenCLSource: isSource = !scanHeaders; break; case CppEditor::ProjectFile::AmbiguousHeader: case CppEditor::ProjectFile::CHeader: case CppEditor::ProjectFile::CXXHeader: case CppEditor::ProjectFile::ObjCHeader: case CppEditor::ProjectFile::ObjCXXHeader: isSource = scanHeaders && !d->pxnodeUtilities->isProxyHeader(file); break; case CppEditor::ProjectFile::Unclassified: case CppEditor::ProjectFile::Unsupported: isSource = false; break; } if (isSource) { component = new qmt::MComponent; component->setFlags(qmt::MElement::ReverseEngineered); component->setName(componentName); } if (component) { QStringList relativeElements = qmt::NameController::buildElementsPath( d->pxnodeUtilities->calcRelativePath(file, anchorFolder), false); if (d->pxnodeUtilities->findSameObject(relativeElements, component)) { delete component; } else { qmt::MPackage *requestedRootPackage = d->diagramSceneController->findSuitableParentPackage(nullptr, diagram); qmt::MPackage *bestParentPackage = d->pxnodeUtilities->createBestMatchingPackagePath(requestedRootPackage, relativeElements); d->diagramSceneController->modelController()->addObject(bestParentPackage, component); } } } for (const QString &fileName : QDir(filePath).entryList(QDir::Dirs|QDir::NoDotAndDotDot)) { QString file = filePath + "/" + fileName; doCreateComponentModel(file, diagram, anchorFolder, scanHeaders); } } } // namespace Internal } // namespace ModelEditor