aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmltyperegistrar/metatypesjsonprocessor.cpp
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2020-11-06 12:33:51 +0100
committerUlf Hermann <ulf.hermann@qt.io>2020-11-06 22:41:51 +0100
commit421ad80dee10e7b5eff352aecb2986c370f0e487 (patch)
tree152b155b255ac99e9dd34f36dc64d047e89ceb57 /src/qmltyperegistrar/metatypesjsonprocessor.cpp
parent68b9ab7b93320a975c2f20c09eddccf0fdb275b7 (diff)
qmltyperegistrar: Move JSON processing into separate class
qmltyperegistrar.cpp was getting unwieldy. Change-Id: I2172253d81c0fca724bb0fef4a96d50a93c47428 Reviewed-by: Maximilian Goldstein <max.goldstein@qt.io> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/qmltyperegistrar/metatypesjsonprocessor.cpp')
-rw-r--r--src/qmltyperegistrar/metatypesjsonprocessor.cpp335
1 files changed, 335 insertions, 0 deletions
diff --git a/src/qmltyperegistrar/metatypesjsonprocessor.cpp b/src/qmltyperegistrar/metatypesjsonprocessor.cpp
new file mode 100644
index 0000000000..f71a94c67d
--- /dev/null
+++ b/src/qmltyperegistrar/metatypesjsonprocessor.cpp
@@ -0,0 +1,335 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "metatypesjsonprocessor.h"
+
+#include <QtCore/qfile.h>
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qqueue.h>
+
+
+bool MetaTypesJsonProcessor::processTypes(const QStringList &files)
+{
+ for (const QString &source: files) {
+ QJsonDocument metaObjects;
+ {
+ QFile f(source);
+ if (!f.open(QIODevice::ReadOnly)) {
+ fprintf(stderr, "Error opening %s for reading\n", qPrintable(source));
+ return false;
+ }
+ QJsonParseError error = {0, QJsonParseError::NoError};
+ metaObjects = QJsonDocument::fromJson(f.readAll(), &error);
+ if (error.error != QJsonParseError::NoError) {
+ fprintf(stderr, "Error parsing %s\n", qPrintable(source));
+ return false;
+ }
+ }
+
+ if (metaObjects.isArray()) {
+ const QJsonArray metaObjectsArray = metaObjects.array();
+ for (const QJsonValue metaObject : metaObjectsArray) {
+ if (!metaObject.isObject()) {
+ fprintf(stderr, "Error parsing %s: JSON is not an object\n",
+ qPrintable(source));
+ return false;
+ }
+
+ processTypes(metaObject.toObject());
+ }
+ } else if (metaObjects.isObject()) {
+ processTypes(metaObjects.object());
+ } else {
+ fprintf(stderr, "Error parsing %s: JSON is not an object or an array\n",
+ qPrintable(source));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool MetaTypesJsonProcessor::processForeignTypes(const QStringList &foreignTypesFiles)
+{
+ bool success = true;
+
+ for (const QString &types : foreignTypesFiles) {
+ QFile typesFile(types);
+ if (!typesFile.open(QIODevice::ReadOnly)) {
+ fprintf(stderr, "Cannot open foreign types file %s\n", qPrintable(types));
+ success = false;
+ continue;
+ }
+
+ QJsonParseError error = {0, QJsonParseError::NoError};
+ QJsonDocument foreignMetaObjects = QJsonDocument::fromJson(typesFile.readAll(), &error);
+ if (error.error != QJsonParseError::NoError) {
+ fprintf(stderr, "Error parsing %s\n", qPrintable(types));
+ success = false;
+ continue;
+ }
+
+ const QJsonArray foreignObjectsArray = foreignMetaObjects.array();
+ for (const QJsonValue metaObject : foreignObjectsArray) {
+ if (!metaObject.isObject()) {
+ fprintf(stderr, "Error parsing %s: JSON is not an object\n",
+ qPrintable(types));
+ success = false;
+ continue;
+ }
+
+ processForeignTypes(metaObject.toObject());
+ }
+ }
+ return success;
+}
+
+void MetaTypesJsonProcessor::postProcessTypes()
+{
+ sortTypes(m_types);
+ sortIncludes();
+}
+
+void MetaTypesJsonProcessor::postProcessForeignTypes()
+{
+ sortTypes(m_foreignTypes);
+ m_types += foreignRelatedTypes();
+ sortTypes(m_types);
+}
+
+MetaTypesJsonProcessor::RegistrationMode MetaTypesJsonProcessor::qmlTypeRegistrationMode(
+ const QJsonObject &classDef)
+{
+ const QJsonArray classInfos = classDef[QLatin1String("classInfos")].toArray();
+ for (const QJsonValue info : classInfos) {
+ const QString name = info[QLatin1String("name")].toString();
+ if (name == QLatin1String("QML.Element")) {
+ if (classDef[QLatin1String("object")].toBool())
+ return ObjectRegistration;
+ if (classDef[QLatin1String("gadget")].toBool())
+ return GadgetRegistration;
+ if (classDef[QLatin1String("namespace")].toBool())
+ return NamespaceRegistration;
+ qWarning() << "Not registering classInfo which is neither an object, "
+ "nor a gadget, nor a namespace:"
+ << name;
+ break;
+ }
+ }
+ return NoRegistration;
+}
+
+QVector<QJsonObject> MetaTypesJsonProcessor::foreignRelatedTypes() const
+{
+ const QLatin1String classInfosKey("classInfos");
+ const QLatin1String nameKey("name");
+ const QLatin1String qualifiedClassNameKey("qualifiedClassName");
+ const QLatin1String qmlNamePrefix("QML.");
+ const QLatin1String qmlForeignName("QML.Foreign");
+ const QLatin1String qmlAttachedName("QML.Attached");
+ const QLatin1String valueKey("value");
+ const QLatin1String superClassesKey("superClasses");
+ const QLatin1String accessKey("access");
+ const QLatin1String publicAccess("public");
+
+ QSet<QString> processedRelatedNames;
+ QQueue<QJsonObject> typeQueue;
+ typeQueue.append(m_types);
+ QVector<QJsonObject> relatedTypes;
+
+ // First mark all classes registered from this module as already processed.
+ for (const QJsonObject &type : m_types) {
+ processedRelatedNames.insert(type.value(qualifiedClassNameKey).toString());
+ const auto classInfos = type.value(classInfosKey).toArray();
+ for (const QJsonValue classInfo : classInfos) {
+ const QJsonObject obj = classInfo.toObject();
+ if (obj.value(nameKey).toString() == qmlForeignName) {
+ processedRelatedNames.insert(obj.value(valueKey).toString());
+ break;
+ }
+ }
+ }
+
+ // Then mark all classes registered from other modules as already processed.
+ // We don't want to generate them again for this module.
+ for (const QJsonObject &foreignType : m_foreignTypes) {
+ const auto classInfos = foreignType.value(classInfosKey).toArray();
+ bool seenQmlPrefix = false;
+ for (const QJsonValue classInfo : classInfos) {
+ const QJsonObject obj = classInfo.toObject();
+ const QString name = obj.value(nameKey).toString();
+ if (!seenQmlPrefix && name.startsWith(qmlNamePrefix)) {
+ processedRelatedNames.insert(foreignType.value(qualifiedClassNameKey).toString());
+ seenQmlPrefix = true;
+ }
+ if (name == qmlForeignName) {
+ processedRelatedNames.insert(obj.value(valueKey).toString());
+ break;
+ }
+ }
+ }
+
+ auto addType = [&](const QString &typeName) {
+ if (processedRelatedNames.contains(typeName))
+ return;
+ processedRelatedNames.insert(typeName);
+ if (const QJsonObject *other
+ = QmlTypesClassDescription::findType(m_foreignTypes, typeName)) {
+ relatedTypes.append(*other);
+ typeQueue.enqueue(*other);
+ }
+ };
+
+ // Then recursively iterate the super types and attached types, marking the
+ // ones we are interested in as related.
+ while (!typeQueue.isEmpty()) {
+ const QJsonObject classDef = typeQueue.dequeue();
+
+ const auto classInfos = classDef.value(classInfosKey).toArray();
+ for (const QJsonValue classInfo : classInfos) {
+ const QJsonObject obj = classInfo.toObject();
+ if (obj.value(nameKey).toString() == qmlAttachedName) {
+ addType(obj.value(valueKey).toString());
+ } else if (obj.value(nameKey).toString() == qmlForeignName) {
+ const QString foreignClassName = obj.value(valueKey).toString();
+ if (const QJsonObject *other = QmlTypesClassDescription::findType(
+ m_foreignTypes, foreignClassName)) {
+ const auto otherSupers = other->value(superClassesKey).toArray();
+ if (!otherSupers.isEmpty()) {
+ const QJsonObject otherSuperObject = otherSupers.first().toObject();
+ if (otherSuperObject.value(accessKey).toString() == publicAccess)
+ addType(otherSuperObject.value(nameKey).toString());
+ }
+
+ const auto otherClassInfos = other->value(classInfosKey).toArray();
+ for (const QJsonValue otherClassInfo : otherClassInfos) {
+ const QJsonObject obj = otherClassInfo.toObject();
+ if (obj.value(nameKey).toString() == qmlAttachedName) {
+ addType(obj.value(valueKey).toString());
+ break;
+ }
+ // No, you cannot chain QML_FOREIGN declarations. Sorry.
+ }
+ break;
+ }
+ }
+ }
+
+ const auto supers = classDef.value(superClassesKey).toArray();
+ if (!supers.isEmpty()) {
+ const QJsonObject superObject = supers.first().toObject();
+ if (superObject.value(accessKey).toString() == publicAccess)
+ addType(superObject.value(nameKey).toString());
+ }
+ }
+
+ return relatedTypes;
+}
+
+void MetaTypesJsonProcessor::sortTypes(QVector<QJsonObject> &types)
+{
+ const QLatin1String qualifiedClassNameKey("qualifiedClassName");
+ std::sort(types.begin(), types.end(), [&](const QJsonObject &a, const QJsonObject &b) {
+ return a.value(qualifiedClassNameKey).toString() <
+ b.value(qualifiedClassNameKey).toString();
+ });
+}
+
+void MetaTypesJsonProcessor::sortIncludes()
+{
+ std::sort(m_includes.begin(), m_includes.end());
+ const auto newEnd = std::unique(m_includes.begin(), m_includes.end());
+ m_includes.erase(newEnd, m_includes.end());
+}
+
+QString MetaTypesJsonProcessor::resolvedInclude(const QString &include)
+{
+ return (m_privateIncludes && include.endsWith(QLatin1String("_p.h")))
+ ? QLatin1String("private/") + include
+ : include;
+}
+
+void MetaTypesJsonProcessor::processTypes(const QJsonObject &types)
+{
+ const QString include = resolvedInclude(types[QLatin1String("inputFile")].toString());
+ const QJsonArray classes = types[QLatin1String("classes")].toArray();
+ for (const QJsonValue cls : classes) {
+ QJsonObject classDef = cls.toObject();
+ classDef.insert(QLatin1String("inputFile"), include);
+
+ switch (qmlTypeRegistrationMode(classDef)) {
+ case NamespaceRegistration:
+ case GadgetRegistration:
+ case ObjectRegistration: {
+ if (!include.endsWith(QLatin1String(".h"))
+ && !include.endsWith(QLatin1String(".hpp"))
+ && !include.endsWith(QLatin1String(".hxx"))
+ && include.contains(QLatin1Char('.'))) {
+ fprintf(stderr,
+ "Class %s is declared in %s, which appears not to be a header.\n"
+ "The compilation of its registration to QML may fail.\n",
+ qPrintable(classDef.value(QLatin1String("qualifiedClassName"))
+ .toString()),
+ qPrintable(include));
+ }
+ m_includes.append(include);
+ {
+ bool shouldRegister = true;
+ for (const QJsonValue v :
+ classDef.value(QLatin1String("classInfos")).toArray()) {
+ if (v[QLatin1String("name")].toString()
+ == QLatin1String("QML.ManualRegistration")) {
+ shouldRegister = QStringView(u"true").compare(
+ v[QLatin1String("value")].toString(),
+ Qt::CaseInsensitive) != 0;
+ }
+ }
+ classDef.insert(QLatin1String("registerable"), shouldRegister);
+ }
+
+ m_types.append(classDef);
+ break;
+ }
+ case NoRegistration:
+ m_foreignTypes.append(classDef);
+ break;
+ }
+ }
+}
+
+void MetaTypesJsonProcessor::processForeignTypes(const QJsonObject &types)
+{
+ const QString include = types[QLatin1String("inputFile")].toString();
+ const QJsonArray classes = types[QLatin1String("classes")].toArray();
+ for (const QJsonValue cls : classes) {
+ QJsonObject classDef = cls.toObject();
+ classDef.insert(QLatin1String("inputFile"), include);
+ m_foreignTypes.append(classDef);
+ }
+}