Revision control
Copy as Markdown
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant
#ifndef DOMTOP_H
#define DOMTOP_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qqmldomitem_p.h"
#include "qqmldomelements_p.h"
#include "qqmldomexternalitems_p.h"
#include <QtCore/QQueue>
#include <QtCore/QString>
#include <QtCore/QDateTime>
#include <QtCore/QCborValue>
#include <QtCore/QCborMap>
#include <memory>
#include <optional>
QT_BEGIN_NAMESPACE
using namespace Qt::Literals::StringLiterals;
namespace QQmlJS {
namespace Dom {
class QMLDOM_EXPORT ExternalItemPairBase: public OwningItem { // all access should have the lock of the DomUniverse containing this
Q_DECLARE_TR_FUNCTIONS(ExternalItemPairBase);
public:
constexpr static DomType kindValue = DomType::ExternalItemPair;
DomType kind() const final override { return kindValue; }
ExternalItemPairBase(
const QDateTime &validExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
const QDateTime ¤tExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
int derivedFrom = 0,
const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
: OwningItem(derivedFrom, lastDataUpdateAt),
validExposedAt(validExposedAt),
currentExposedAt(currentExposedAt)
{}
ExternalItemPairBase(const ExternalItemPairBase &o):
OwningItem(o), validExposedAt(o.validExposedAt), currentExposedAt(o.currentExposedAt)
{}
virtual std::shared_ptr<ExternalOwningItem> validItem() const = 0;
virtual DomItem validItem(const DomItem &self) const = 0;
virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0;
virtual DomItem currentItem(const DomItem &self) const = 0;
QString canonicalFilePath(const DomItem &) const final override;
Path canonicalPath(const DomItem &self) const final override;
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const final override;
DomItem field(const DomItem &self, QStringView name) const final override
{
return OwningItem::field(self, name);
}
bool currentIsValid() const;
std::shared_ptr<ExternalItemPairBase> makeCopy(const DomItem &self) const
{
return std::static_pointer_cast<ExternalItemPairBase>(doCopy(self));
}
QDateTime lastDataUpdateAt() const final override
{
if (currentItem())
return currentItem()->lastDataUpdateAt();
return ExternalItemPairBase::lastDataUpdateAt();
}
void refreshedDataAt(QDateTime tNew) final override
{
if (currentItem())
currentItem()->refreshedDataAt(tNew);
return OwningItem::refreshedDataAt(tNew);
}
friend class DomUniverse;
QDateTime validExposedAt;
QDateTime currentExposedAt;
};
template<class T>
class QMLDOM_EXPORT ExternalItemPair final : public ExternalItemPairBase
{ // all access should have the lock of the DomUniverse containing this
protected:
std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
{
return std::make_shared<ExternalItemPair>(*this);
}
public:
constexpr static DomType kindValue = DomType::ExternalItemPair;
friend class DomUniverse;
ExternalItemPair(
const std::shared_ptr<T> &valid = {}, const std::shared_ptr<T> ¤t = {},
const QDateTime &validExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
const QDateTime ¤tExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
int derivedFrom = 0,
const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
: ExternalItemPairBase(validExposedAt, currentExposedAt, derivedFrom, lastDataUpdateAt),
valid(valid),
current(current)
{}
ExternalItemPair(const ExternalItemPair &o):
ExternalItemPairBase(o), valid(o.valid), current(o.current)
{
}
std::shared_ptr<ExternalOwningItem> validItem() const override { return valid; }
DomItem validItem(const DomItem &self) const override { return self.copy(valid); }
std::shared_ptr<ExternalOwningItem> currentItem() const override { return current; }
DomItem currentItem(const DomItem &self) const override { return self.copy(current); }
std::shared_ptr<ExternalItemPair> makeCopy(const DomItem &self) const
{
return std::static_pointer_cast<ExternalItemPair>(doCopy(self));
}
std::shared_ptr<T> valid;
std::shared_ptr<T> current;
};
class QMLDOM_EXPORT DomTop: public OwningItem {
public:
DomTop(QMap<QString, OwnerT> extraOwningItems = {}, int derivedFrom = 0)
: OwningItem(derivedFrom), m_extraOwningItems(extraOwningItems)
{}
DomTop(const DomTop &o):
OwningItem(o)
{
QMap<QString, OwnerT> items = o.extraOwningItems();
{
QMutexLocker l(mutex());
m_extraOwningItems = items;
}
}
using Callback = DomItem::Callback;
virtual Path canonicalPath() const = 0;
Path canonicalPath(const DomItem &) const override;
DomItem containingObject(const DomItem &) const override;
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
template<typename T>
void setExtraOwningItem(const QString &fieldName, const std::shared_ptr<T> &item)
{
QMutexLocker l(mutex());
if (!item)
m_extraOwningItems.remove(fieldName);
else
m_extraOwningItems.insert(fieldName, item);
}
void clearExtraOwningItems();
QMap<QString, OwnerT> extraOwningItems() const;
private:
QMap<QString, OwnerT> m_extraOwningItems;
};
class QMLDOM_EXPORT DomUniverse final : public DomTop,
public std::enable_shared_from_this<DomUniverse>
{
Q_GADGET
Q_DECLARE_TR_FUNCTIONS(DomUniverse);
protected:
std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override;
public:
constexpr static DomType kindValue = DomType::DomUniverse;
DomType kind() const override { return kindValue; }
static ErrorGroups myErrors();
DomUniverse(const QString &universeName);
DomUniverse(const DomUniverse &) = delete;
static std::shared_ptr<DomUniverse> guaranteeUniverse(const std::shared_ptr<DomUniverse> &univ);
static DomItem create(const QString &universeName);
Path canonicalPath() const override;
using DomTop::canonicalPath;
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
std::shared_ptr<DomUniverse> makeCopy(const DomItem &self) const
{
return std::static_pointer_cast<DomUniverse>(doCopy(self));
}
// Helper structure reflecting the change in the map once loading && parsing is completed
// formerItem - DomItem representing value (ExternalItemPair) existing in the map before the
// loading && parsing. Might be empty (if didn't exist / failure) or equal to currentItem
// currentItem - DomItem representing current map value
struct LoadResult
{
DomItem formerItem;
DomItem currentItem;
};
LoadResult loadFile(const FileToLoad &file, DomType fileType,
DomCreationOption creationOption = {});
void removePath(const QString &dir);
std::shared_ptr<ExternalItemPair<QmlFile>> qmlFileWithPath(const QString &path) const
{
QMutexLocker l(mutex());
return m_qmlFileWithPath.value(path);
}
QSet<QString> qmlFilePaths() const
{
QMap<QString, std::shared_ptr<ExternalItemPair<QmlFile>>> map;
{
QMutexLocker l(mutex());
map = m_qmlFileWithPath;
}
return QSet<QString>(map.keyBegin(), map.keyEnd());
}
std::shared_ptr<ExternalItemPair<JsFile>> jsFileWithPath(const QString &path) const
{
QMutexLocker l(mutex());
return m_jsFileWithPath.value(path);
}
QSet<QString> jsFilePaths() const
{
QMap<QString, std::shared_ptr<ExternalItemPair<JsFile>>> map;
{
QMutexLocker l(mutex());
map = m_jsFileWithPath;
}
return QSet<QString>(map.keyBegin(), map.keyEnd());
}
QString name() const {
return m_name;
}
private:
struct ContentWithDate
{
QString content;
QDateTime date;
};
// contains either Content with the timestamp when it was read or an Error
using ReadResult = std::variant<ContentWithDate, ErrorMessage>;
ReadResult readFileContent(const QString &canonicalPath) const;
LoadResult load(const ContentWithDate &codeWithDate, const FileToLoad &file, DomType fType,
DomCreationOption creationOption = {});
// contains either Content to be parsed or LoadResult if loading / parsing is not needed
using PreloadResult = std::variant<ContentWithDate, LoadResult>;
PreloadResult preload(const DomItem &univ, const FileToLoad &file, DomType fType) const;
std::shared_ptr<QmlFile> parseQmlFile(const QString &code, const FileToLoad &file,
const QDateTime &contentDate,
DomCreationOption creationOption);
std::shared_ptr<JsFile> parseJsFile(const QString &code, const FileToLoad &file,
const QDateTime &contentDate);
std::shared_ptr<ExternalItemPairBase> getPathValueOrNull(DomType fType,
const QString &path) const;
std::optional<DomItem> getItemIfMostRecent(const DomItem &univ, DomType fType,
const QString &path) const;
std::optional<DomItem> getItemIfHasSameCode(const DomItem &univ, DomType fType,
const QString &canonicalPath,
const ContentWithDate &codeWithDate) const;
static bool valueHasMostRecentItem(const ExternalItemPairBase *value,
const QDateTime &lastModified);
static bool valueHasSameContent(const ExternalItemPairBase *value, const QString &content);
// TODO better name / consider proper public get/set
template <typename T>
QMap<QString, std::shared_ptr<ExternalItemPair<T>>> &getMutableRefToMap()
{
Q_ASSERT(!mutex()->tryLock());
if constexpr (std::is_same_v<T, QmlFile>) {
return m_qmlFileWithPath;
}
if constexpr (std::is_same_v<T, JsFile>) {
return m_jsFileWithPath;
}
Q_UNREACHABLE();
}
// Inserts or updates an entry reflecting ExternalItem in the corresponding map
// Returns a pair of:
// - current ExternalItemPair, current value in the map (might be empty, or equal to curValue)
// - new current ExternalItemPair, value in the map after after the execution of this function
template <typename T>
std::pair<std::shared_ptr<ExternalItemPair<T>>, std::shared_ptr<ExternalItemPair<T>>>
insertOrUpdateEntry(std::shared_ptr<T> newItem)
{
std::shared_ptr<ExternalItemPair<T>> curValue;
std::shared_ptr<ExternalItemPair<T>> newCurValue;
QString canonicalPath = newItem->canonicalFilePath();
QDateTime now = QDateTime::currentDateTimeUtc();
{
QMutexLocker l(mutex());
auto &map = getMutableRefToMap<T>();
auto it = map.find(canonicalPath);
if (it != map.cend() && (*it) && (*it)->current) {
curValue = *it;
if (valueHasSameContent(curValue.get(), newItem->code())) {
// value in the map has same content as newItem, a.k.a. most recent
newCurValue = curValue;
if (newCurValue->current->lastDataUpdateAt() < newItem->lastDataUpdateAt()) {
// update timestamp in the current, as if its content was refreshed by
// NewItem
newCurValue->current->refreshedDataAt(newItem->lastDataUpdateAt());
}
} else if (curValue->current->lastDataUpdateAt() > newItem->lastDataUpdateAt()) {
// value in the map is more recent than newItem, nothing to update
newCurValue = curValue;
} else {
// perform update with newItem
curValue->current = std::move(newItem);
curValue->currentExposedAt = now;
if (curValue->current->isValid()) {
curValue->valid = curValue->current;
curValue->validExposedAt = std::move(now);
}
newCurValue = curValue;
}
} else {
// not found / invalid, just insert
newCurValue = std::make_shared<ExternalItemPair<T>>(
(newItem->isValid() ? newItem : std::shared_ptr<T>()), newItem, now, now);
map.insert(canonicalPath, newCurValue);
}
}
return std::make_pair(curValue, newCurValue);
}
// Inserts or updates an entry reflecting ExternalItem in the corresponding map
// returns LoadResult reflecting the change made to the map
template <typename T>
LoadResult insertOrUpdateExternalItem(std::shared_ptr<T> extItem)
{
auto change = insertOrUpdateEntry<T>(std::move(extItem));
DomItem univ(shared_from_this());
return { univ.copy(change.first), univ.copy(change.second) };
}
private:
QString m_name;
QMap<QString, std::shared_ptr<ExternalItemPair<QmlFile>>> m_qmlFileWithPath;
QMap<QString, std::shared_ptr<ExternalItemPair<JsFile>>> m_jsFileWithPath;
};
class QMLDOM_EXPORT ExternalItemInfoBase: public OwningItem {
Q_DECLARE_TR_FUNCTIONS(ExternalItemInfoBase);
public:
constexpr static DomType kindValue = DomType::ExternalItemInfo;
DomType kind() const final override { return kindValue; }
ExternalItemInfoBase(
const Path &canonicalPath,
const QDateTime ¤tExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
int derivedFrom = 0,
const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
: OwningItem(derivedFrom, lastDataUpdateAt),
m_canonicalPath(canonicalPath),
m_currentExposedAt(currentExposedAt)
{}
ExternalItemInfoBase(const ExternalItemInfoBase &o) = default;
virtual std::shared_ptr<ExternalOwningItem> currentItem() const = 0;
virtual DomItem currentItem(const DomItem &) const = 0;
QString canonicalFilePath(const DomItem &) const final override;
Path canonicalPath() const { return m_canonicalPath; }
Path canonicalPath(const DomItem &) const final override { return canonicalPath(); }
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const final override;
DomItem field(const DomItem &self, QStringView name) const final override
{
return OwningItem::field(self, name);
}
int currentRevision(const DomItem &self) const;
int lastRevision(const DomItem &self) const;
int lastValidRevision(const DomItem &self) const;
std::shared_ptr<ExternalItemInfoBase> makeCopy(const DomItem &self) const
{
return std::static_pointer_cast<ExternalItemInfoBase>(doCopy(self));
}
QDateTime lastDataUpdateAt() const final override
{
if (currentItem())
return currentItem()->lastDataUpdateAt();
return OwningItem::lastDataUpdateAt();
}
void refreshedDataAt(QDateTime tNew) final override
{
if (currentItem())
currentItem()->refreshedDataAt(tNew);
return OwningItem::refreshedDataAt(tNew);
}
void ensureLogicalFilePath(const QString &path) {
QMutexLocker l(mutex());
if (!m_logicalFilePaths.contains(path))
m_logicalFilePaths.append(path);
}
QDateTime currentExposedAt() const {
QMutexLocker l(mutex()); // should not be needed, as it should not change...
return m_currentExposedAt;
}
void setCurrentExposedAt(QDateTime d) {
QMutexLocker l(mutex()); // should not be needed, as it should not change...
m_currentExposedAt = d;
}
QStringList logicalFilePaths() const {
QMutexLocker l(mutex());
return m_logicalFilePaths;
}
private:
friend class DomEnvironment;
Path m_canonicalPath;
QDateTime m_currentExposedAt;
QStringList m_logicalFilePaths;
};
template<typename T>
class ExternalItemInfo final : public ExternalItemInfoBase
{
protected:
std::shared_ptr<OwningItem> doCopy(const DomItem &) const override
{
return std::make_shared<ExternalItemInfo>(*this);
}
public:
constexpr static DomType kindValue = DomType::ExternalItemInfo;
ExternalItemInfo(
const std::shared_ptr<T> ¤t = std::shared_ptr<T>(),
const QDateTime ¤tExposedAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC),
int derivedFrom = 0,
const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
: ExternalItemInfoBase(current->canonicalPath().dropTail(), currentExposedAt, derivedFrom,
lastDataUpdateAt),
current(current)
{}
ExternalItemInfo(const QString &canonicalPath) : current(new T(canonicalPath)) { }
ExternalItemInfo(const ExternalItemInfo &o):
ExternalItemInfoBase(o), current(o.current)
{
}
std::shared_ptr<ExternalItemInfo> makeCopy(const DomItem &self) const
{
return std::static_pointer_cast<ExternalItemInfo>(doCopy(self));
}
std::shared_ptr<ExternalOwningItem> currentItem() const override {
return current;
}
DomItem currentItem(const DomItem &self) const override { return self.copy(current); }
std::shared_ptr<T> current;
};
class Dependency
{ // internal, should be cleaned, but nobody should use this...
public:
bool operator==(Dependency const &o) const
{
return uri == o.uri && version.majorVersion == o.version.majorVersion
&& version.minorVersion == o.version.minorVersion && filePath == o.filePath;
}
QString uri; // either dotted uri or file:, http: https: uri
Version version;
QString filePath; // for file deps
DomType fileType;
};
class QMLDOM_EXPORT LoadInfo final : public OwningItem
{
Q_DECLARE_TR_FUNCTIONS(LoadInfo);
protected:
std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override;
public:
constexpr static DomType kindValue = DomType::LoadInfo;
DomType kind() const override { return kindValue; }
enum class Status {
NotStarted, // dependencies non checked yet
Starting, // adding deps
InProgress, // waiting for all deps to be loaded
CallingCallbacks, // calling callbacks
Done // fully loaded
};
LoadInfo(const Path &elPath = Path(), Status status = Status::NotStarted, int nLoaded = 0,
int derivedFrom = 0,
const QDateTime &lastDataUpdateAt = QDateTime::fromMSecsSinceEpoch(0, QTimeZone::UTC))
: OwningItem(derivedFrom, lastDataUpdateAt),
m_elementCanonicalPath(elPath),
m_status(status),
m_nLoaded(nLoaded)
{
}
LoadInfo(const LoadInfo &o) : OwningItem(o), m_elementCanonicalPath(o.elementCanonicalPath())
{
{
QMutexLocker l(o.mutex());
m_status = o.m_status;
m_nLoaded = o.m_nLoaded;
m_toDo = o.m_toDo;
m_inProgress = o.m_inProgress;
m_endCallbacks = o.m_endCallbacks;
}
}
Path canonicalPath(const DomItem &self) const override;
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
std::shared_ptr<LoadInfo> makeCopy(const DomItem &self) const
{
return std::static_pointer_cast<LoadInfo>(doCopy(self));
}
void addError(const DomItem &self, ErrorMessage &&msg) override
{
self.path(elementCanonicalPath()).addError(std::move(msg));
}
void addEndCallback(const DomItem &self, std::function<void(Path, const DomItem &, const DomItem &)> callback);
void advanceLoad(const DomItem &self);
void finishedLoadingDep(const DomItem &self, const Dependency &d);
void execEnd(const DomItem &self);
Status status() const
{
QMutexLocker l(mutex());
return m_status;
}
int nLoaded() const
{
QMutexLocker l(mutex());
return m_nLoaded;
}
Path elementCanonicalPath() const
{
QMutexLocker l(mutex()); // we should never change this, remove lock?
return m_elementCanonicalPath;
}
int nNotDone() const
{
QMutexLocker l(mutex());
return m_toDo.size() + m_inProgress.size();
}
QList<Dependency> inProgress() const
{
QMutexLocker l(mutex());
return m_inProgress;
}
QList<Dependency> toDo() const
{
QMutexLocker l(mutex());
return m_toDo;
}
int nCallbacks() const
{
QMutexLocker l(mutex());
return m_endCallbacks.size();
}
private:
void doAddDependencies(const DomItem &self);
void addDependency(const DomItem &self, const Dependency &dep);
Path m_elementCanonicalPath;
Status m_status;
int m_nLoaded;
QQueue<Dependency> m_toDo;
QList<Dependency> m_inProgress;
QList<std::function<void(Path, const DomItem &, const DomItem &)>> m_endCallbacks;
};
enum class EnvLookup { Normal, NoBase, BaseOnly };
enum class Changeable { ReadOnly, Writable };
class QMLDOM_EXPORT RefCacheEntry
{
Q_GADGET
public:
enum class Cached { None, First, All };
Q_ENUM(Cached)
static RefCacheEntry forPath(const DomItem &el, const Path &canonicalPath);
static bool addForPath(const DomItem &el, const Path &canonicalPath, const RefCacheEntry &entry,
AddOption addOption = AddOption::KeepExisting);
Cached cached = Cached::None;
QList<Path> canonicalPaths;
};
class QMLDOM_EXPORT DomEnvironment final : public DomTop,
public std::enable_shared_from_this<DomEnvironment>
{
Q_GADGET
Q_DECLARE_TR_FUNCTIONS(DomEnvironment);
protected:
std::shared_ptr<OwningItem> doCopy(const DomItem &self) const override;
private:
struct TypeReader
{
TypeReader(const std::weak_ptr<DomEnvironment> &env, const QStringList &importPaths)
: m_env(env), m_importPaths(importPaths)
{
}
std::weak_ptr<DomEnvironment> m_env;
QStringList m_importPaths;
void operator()(QQmlJSImporter *importer, const QString &filePath,
const QSharedPointer<QQmlJSScope> &scopeToPopulate);
};
public:
enum class Option {
Default = 0x0,
KeepValid = 0x1, // if there is a previous valid version, use that instead of the latest
Exported = 0x2, // the current environment is accessible by multiple threads, one should only modify whole OwningItems, and in general load and do other operations in other (Child) environments
NoReload = 0x4, // never reload something that was already loaded by the parent environment
WeakLoad = 0x8, // load only the names of the available types, not the types (qml files) themselves
SingleThreaded = 0x10, // do all operations in a single thread
NoDependencies = 0x20 // will not load dependencies (useful when editing)
};
Q_ENUM(Option)
Q_DECLARE_FLAGS(Options, Option);
static ErrorGroups myErrors();
constexpr static DomType kindValue = DomType::DomEnvironment;
DomType kind() const override;
Path canonicalPath() const override;
using DomTop::canonicalPath;
bool iterateDirectSubpaths(const DomItem &self, DirectVisitor) const override;
DomItem field(const DomItem &self, QStringView name) const final override;
std::shared_ptr<DomEnvironment> makeCopy(const DomItem &self) const;
bool hasFile(const QString &filePath) const;
void loadFile(const FileToLoad &file, const Callback &callback,
std::optional<DomType> fileType = std::optional<DomType>(),
const ErrorHandler &h = nullptr /* used only in loadPendingDependencies*/);
void removePath(const QString &path);
std::shared_ptr<DomUniverse> universe() const;
QSet<QString> moduleIndexUris(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const;
QSet<int> moduleIndexMajorVersions(const DomItem &self, const QString &uri,
EnvLookup lookup = EnvLookup::Normal) const;
std::shared_ptr<ExternalItemInfoBase>
qmlDirWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const;
std::shared_ptr<ExternalItemInfo<QmlFile>>
qmlFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const;
QSet<QString> qmlFilePaths(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const;
std::shared_ptr<ExternalItemInfo<JsFile>>
jsFileWithPath(const DomItem &self, const QString &path, EnvLookup options = EnvLookup::Normal) const;
QSet<QString> jsFilePaths(const DomItem &self, EnvLookup lookup = EnvLookup::Normal) const;
explicit DomEnvironment(const QStringList &loadPaths, Options options = Option::SingleThreaded,
DomCreationOption domCreationOption = Default,
const std::shared_ptr<DomUniverse> &universe = nullptr);
explicit DomEnvironment(const std::shared_ptr<DomEnvironment> &parent,
const QStringList &loadPaths, Options options = Option::SingleThreaded,
DomCreationOption domCreationOption = Default);
DomEnvironment(const DomEnvironment &o) = delete;
static std::shared_ptr<DomEnvironment>
create(const QStringList &loadPaths, Options options = Option::SingleThreaded,
DomCreationOption creationOption = DomCreationOption::Default,
const DomItem &universe = DomItem::empty);
// TODO AddOption can easily be removed later. KeepExisting option only used in one
void addQmlFile(const std::shared_ptr<QmlFile> &file,
AddOption option = AddOption::KeepExisting);
void addJsFile(const std::shared_ptr<JsFile> &file, AddOption option = AddOption::KeepExisting);
bool commitToBase(
const DomItem &self, const std::shared_ptr<DomEnvironment> &validEnv = nullptr);
void addDependenciesToLoad(const Path &path);
void addLoadInfo(
const DomItem &self, const std::shared_ptr<LoadInfo> &loadInfo);
std::shared_ptr<LoadInfo> loadInfo(const Path &path) const;
QList<Path> loadInfoPaths() const;
QHash<Path, std::shared_ptr<LoadInfo>> loadInfos() const;
void loadPendingDependencies();
bool finishLoadingDependencies(int waitMSec = 30000);
void addWorkForLoadInfo(const Path &elementCanonicalPath);
Options options() const;
std::shared_ptr<DomEnvironment> base() const;
QStringList loadPaths() const;
static QList<Import> defaultImplicitImports();
QList<Import> implicitImports() const;
void addAllLoadedCallback(const DomItem &self, Callback c);
void clearReferenceCache();
void setLoadPaths(const QStringList &v);
void setResourceFiles(const QStringList &v);
// Helper structure reflecting the change in the map once loading / fetching is completed
// formerItem - DomItem representing value (ExternalItemInfo) existing in the map before the
// loading && parsing. Might be empty (if didn't exist / failure) or equal to currentItem
// currentItem - DomItem representing current map value
struct LoadResult
{
DomItem formerItem;
DomItem currentItem;
};
// TODO(QTBUG-121171)
template <typename T>
LoadResult insertOrUpdateExternalItemInfo(const QString &path, std::shared_ptr<T> extItem)
{
// maybe in the next revision this all can be just substituted by the addExternalItem
DomItem env(shared_from_this());
// try to fetch from the current env.
if (auto curValue = lookup<T>(path, EnvLookup::NoBase)) {
// found in the "initial" env
return { env.copy(curValue), env.copy(curValue) };
}
std::shared_ptr<ExternalItemInfo<T>> newCurValue;
// try to fetch from the base env
auto valueInBase = lookup<T>(path, EnvLookup::BaseOnly);
if (!valueInBase) {
// Nothing found. Just create an externalItemInfo which will be inserted
newCurValue = std::make_shared<ExternalItemInfo<T>>(std::move(extItem),
QDateTime::currentDateTimeUtc());
} else {
// prepare updated value as a copy of the value from the Base to be inserted
newCurValue = valueInBase->makeCopy(env);
if (newCurValue->current != extItem) {
newCurValue->current = std::move(extItem);
newCurValue->setCurrentExposedAt(QDateTime::currentDateTimeUtc());
}
}
// Before inserting new or updated value, check one more time, if ItemInfo is already
// present
// lookup<> can't be used here because of the data-race
{
QMutexLocker l(mutex());
auto &map = getMutableRefToMap<T>();
const auto &it = map.find(path);
if (it != map.end())
return { env.copy(*it), env.copy(*it) };
// otherwise insert
map.insert(path, newCurValue);
}
return { env.copy(valueInBase), env.copy(newCurValue) };
}
template <typename T>
void addExternalItemInfo(const DomItem &newExtItem, const Callback &loadCallback,
const Callback &endCallback)
{
// get either Valid "file" from the ExternalItemPair or the current (wip) "file"
std::shared_ptr<T> newItemPtr;
if (options() & DomEnvironment::Option::KeepValid)
newItemPtr = newExtItem.field(Fields::validItem).ownerAs<T>();
if (!newItemPtr)
newItemPtr = newExtItem.field(Fields::currentItem).ownerAs<T>();
Q_ASSERT(newItemPtr && "envCallbackForFile reached without current file");
auto loadResult = insertOrUpdateExternalItemInfo(newExtItem.canonicalFilePath(),
std::move(newItemPtr));
Path p = loadResult.currentItem.canonicalPath();
{
auto depLoad = qScopeGuard([p, this, endCallback] {
addDependenciesToLoad(p);
// add EndCallback to the queue, which should be called once all dependencies are
// loaded
if (endCallback) {
DomItem env = DomItem(shared_from_this());
addAllLoadedCallback(
env, [p, endCallback](Path, const DomItem &, const DomItem &env) {
DomItem el = env.path(p);
endCallback(p, el, el);
});
}
});
// call loadCallback
if (loadCallback) {
loadCallback(p, loadResult.formerItem, loadResult.currentItem);
}
}
}
void populateFromQmlFile(MutableDomItem &&qmlFile);
DomCreationOption domCreationOption() const { return m_domCreationOption; }
private:
friend class RefCacheEntry;
void loadFile(const FileToLoad &file, const Callback &loadCallback, const Callback &endCallback,
std::optional<DomType> fileType = std::optional<DomType>(),
const ErrorHandler &h = nullptr);
template <typename T>
QSet<QString> getStrings(function_ref<QSet<QString>()> getBase, const QMap<QString, T> &selfMap,
EnvLookup lookup) const;
template <typename T>
const QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &getConstRefToMap() const
{
Q_ASSERT(!mutex()->tryLock());
if constexpr (std::is_same_v<T, QmlFile>) {
return m_qmlFileWithPath;
}
if constexpr (std::is_same_v<T, JsFile>) {
return m_jsFileWithPath;
}
Q_UNREACHABLE();
}
template <typename T>
std::shared_ptr<ExternalItemInfo<T>> lookup(const QString &path, EnvLookup options) const
{
if (options != EnvLookup::BaseOnly) {
QMutexLocker l(mutex());
const auto &map = getConstRefToMap<T>();
const auto &it = map.find(path);
if (it != map.end())
return *it;
}
if (options != EnvLookup::NoBase && m_base)
return m_base->lookup<T>(path, options);
return {};
}
template <typename T>
QMap<QString, std::shared_ptr<ExternalItemInfo<T>>> &getMutableRefToMap()
{
Q_ASSERT(!mutex()->tryLock());
if constexpr (std::is_same_v<T, QmlFile>) {
return m_qmlFileWithPath;
}
if constexpr (std::is_same_v<T, JsFile>) {
return m_jsFileWithPath;
}
Q_UNREACHABLE();
}
template <typename T>
void addExternalItem(std::shared_ptr<T> file, QString key, AddOption option)
{
if (!file)
return;
auto eInfo = std::make_shared<ExternalItemInfo<T>>(file, QDateTime::currentDateTimeUtc());
// Lookup helper can't be used here, because it introduces data-race otherwise
// (other modifications might happen between the lookup and the insert)
QMutexLocker l(mutex());
auto &map = getMutableRefToMap<T>();
const auto &it = map.find(key);
if (it != map.end() && option == AddOption::KeepExisting)
return;
map.insert(key, eInfo);
}
using FetchResult =
std::pair<std::shared_ptr<ExternalItemInfoBase>, std::shared_ptr<ExternalItemInfoBase>>;
// This function tries to get an Info object about the ExternalItem from the current env
// and depending on the result and options tries to fetch it from the Parent env,
// saving a copy with an updated timestamp
template <typename T>
FetchResult fetchFileFromEnvs(const FileToLoad &file)
{
const auto &path = file.canonicalPath();
// lookup only in the current env
if (auto value = lookup<T>(path, EnvLookup::NoBase)) {
return std::make_pair(value, value);
}
// try to find the file in the base(parent) Env and insert if found
if (options() & Option::NoReload) {
if (auto baseV = lookup<T>(path, EnvLookup::BaseOnly)) {
// Watch out! QTBUG-121171
// It's possible between the lookup and creation of curVal, baseV && baseV->current
// might have changed
// Prepare a value to be inserted as copy of the value from Base
auto curV = std::make_shared<ExternalItemInfo<T>>(
baseV->current, QDateTime::currentDateTimeUtc(), baseV->revision(),
baseV->lastDataUpdateAt());
// Lookup one more time if the value was already inserted to the current env
// Lookup can't be used here because of the data-race
{
QMutexLocker l(mutex());
auto &map = getMutableRefToMap<T>();
const auto &it = map.find(path);
if (it != map.end())
return std::make_pair(*it, *it);
// otherwise insert
map.insert(path, curV);
}
return std::make_pair(baseV, curV);
}
}
return std::make_pair(nullptr, nullptr);
}
struct SemanticAnalysis
{
SemanticAnalysis(const QStringList &loadPaths);
void updateLoadPaths(const QStringList &loadPaths);
void setResourceFiles(const QStringList &qrcFiles);
std::shared_ptr<QQmlJSResourceFileMapper> m_mapper;
std::shared_ptr<QQmlJSImporter> m_importer;
QStringList m_resourceFiles;
};
public:
SemanticAnalysis semanticAnalysis();
private:
SemanticAnalysis semanticAnalysisUnlocked();
SemanticAnalysis prepareSemanticAnalysisForFileLoading();
const Options m_options;
const std::shared_ptr<DomEnvironment> m_base;
std::shared_ptr<DomEnvironment> m_lastValidBase;
const std::shared_ptr<DomUniverse> m_universe;
QStringList m_loadPaths; // paths for qml
QStringList m_resourcePaths;
QMap<QString, std::shared_ptr<ExternalItemInfo<QmlFile>>> m_qmlFileWithPath;
QMap<QString, std::shared_ptr<ExternalItemInfo<JsFile>>> m_jsFileWithPath;
QQueue<Path> m_loadsWithWork;
QQueue<Path> m_inProgress;
QHash<Path, std::shared_ptr<LoadInfo>> m_loadInfos;
QList<Import> m_implicitImports;
QList<Callback> m_allLoadedCallback;
QHash<Path, RefCacheEntry> m_referenceCache;
DomCreationOption m_domCreationOption;
std::optional<SemanticAnalysis> m_semanticAnalysis;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(DomEnvironment::Options)
} // end namespace Dom
} // end namespace QQmlJS
QT_END_NAMESPACE
#endif // DOMTOP_H