1 | // Copyright (C) 2019 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "qqmltypesclassdescription_p.h" |
5 | |
6 | #include "qqmltypescreator_p.h" |
7 | #include "qmetatypesjsonprocessor_p.h" |
8 | |
9 | #include <QtCore/qjsonarray.h> |
10 | |
11 | QT_BEGIN_NAMESPACE |
12 | |
13 | static QString qualifiedClassName(const QJsonObject &classDef) |
14 | { |
15 | return classDef.value(key: QLatin1String("qualifiedClassName" )).toString(); |
16 | } |
17 | |
18 | static void (const QJsonObject *component, const QString &key, |
19 | QList<QTypeRevision> &) |
20 | { |
21 | const QJsonArray &items = component->value(key).toArray(); |
22 | for (const QJsonValue item : items) { |
23 | const QJsonObject obj = item.toObject(); |
24 | const auto revision = obj.find(key: QLatin1String("revision" )); |
25 | if (revision != obj.end()) { |
26 | const auto = QTypeRevision::fromEncodedVersion(value: revision.value().toInt()); |
27 | if (!extraVersions.contains(t: extraVersion)) |
28 | extraVersions.append(t: extraVersion); |
29 | } |
30 | } |
31 | } |
32 | |
33 | const QJsonObject *QmlTypesClassDescription::findType( |
34 | const QVector<QJsonObject> &types, const QVector<QJsonObject> &foreign, |
35 | const QString &name, const QStringList &namespaces) |
36 | { |
37 | const auto compare = [](const QJsonObject &type, const QString &typeName) { |
38 | return qualifiedClassName(classDef: type) < typeName; |
39 | }; |
40 | const auto tryFindType = [&](const QString &qualifiedName) -> const QJsonObject * { |
41 | for (const QVector<QJsonObject> &t : {types, foreign}) { |
42 | const auto it = std::lower_bound(first: t.begin(), last: t.end(), val: qualifiedName, comp: compare); |
43 | if (it != t.end() && qualifiedClassName(classDef: *it) == qualifiedName) |
44 | return &(*it); |
45 | } |
46 | return nullptr; |
47 | }; |
48 | if (name.startsWith(s: QLatin1String("::" ))) |
49 | return tryFindType(name.mid(position: 2)); |
50 | QString qualified; |
51 | for (int i = 0, end = namespaces.length(); i != end; ++i) { |
52 | for (int j = 0; j < end - i; ++j) { |
53 | qualified.append(s: namespaces[j]); |
54 | qualified.append(s: QLatin1String("::" )); |
55 | } |
56 | qualified.append(s: name); |
57 | if (const QJsonObject *found = tryFindType(qualified)) |
58 | return found; |
59 | qualified.truncate(pos: 0); |
60 | } |
61 | return tryFindType(name); |
62 | } |
63 | |
64 | void QmlTypesClassDescription::collectSuperClasses( |
65 | const QJsonObject *classDef, const QVector<QJsonObject> &types, |
66 | const QVector<QJsonObject> &foreign, CollectMode mode, QTypeRevision defaultRevision) |
67 | { |
68 | const QStringList namespaces = MetaTypesJsonProcessor::namespaces(classDef: *classDef); |
69 | const auto supers = classDef->value(key: QLatin1String("superClasses" )).toArray(); |
70 | for (const QJsonValue superValue : supers) { |
71 | const QJsonObject superObject = superValue.toObject(); |
72 | if (superObject[QLatin1String("access" )].toString() == QLatin1String("public" )) { |
73 | const QString superName = superObject[QLatin1String("name" )].toString(); |
74 | |
75 | const CollectMode superMode = (mode == TopLevel) ? SuperClass : RelatedType; |
76 | if (const QJsonObject *other = findType(types, foreign, name: superName, namespaces)) { |
77 | collect(classDef: other, types, foreign, mode: superMode, defaultRevision); |
78 | if (mode == TopLevel && superClass.isEmpty()) |
79 | superClass = qualifiedClassName(classDef: *other); |
80 | } |
81 | |
82 | // If we cannot locate a type for it, there is no point in recording the superClass |
83 | } |
84 | } |
85 | } |
86 | |
87 | void QmlTypesClassDescription::collectInterfaces(const QJsonObject *classDef) |
88 | { |
89 | if (classDef->contains(key: QLatin1String("interfaces" ))) { |
90 | const QJsonArray array = classDef->value(key: QLatin1String("interfaces" )).toArray(); |
91 | for (const QJsonValue value : array) { |
92 | auto object = value.toArray()[0].toObject(); |
93 | implementsInterfaces << object[QLatin1String("className" )].toString(); |
94 | } |
95 | } |
96 | } |
97 | |
98 | void QmlTypesClassDescription::collectLocalAnonymous( |
99 | const QJsonObject *classDef, const QVector<QJsonObject> &types, |
100 | const QVector<QJsonObject> &foreign, QTypeRevision defaultRevision) |
101 | { |
102 | file = classDef->value(key: QLatin1String("inputFile" )).toString(); |
103 | |
104 | resolvedClass = classDef; |
105 | className = qualifiedClassName(classDef: *classDef); |
106 | |
107 | if (classDef->value(QStringLiteral("object" )).toBool()) |
108 | accessSemantics = QStringLiteral("reference" ); |
109 | else if (classDef->value(QStringLiteral("gadget" )).toBool()) |
110 | accessSemantics = QStringLiteral("value" ); |
111 | else |
112 | accessSemantics = QStringLiteral("none" ); |
113 | |
114 | const auto classInfos = classDef->value(key: QLatin1String("classInfos" )).toArray(); |
115 | for (const QJsonValue classInfo : classInfos) { |
116 | const QJsonObject obj = classInfo.toObject(); |
117 | if (obj[QStringLiteral("name" )].toString() == QStringLiteral("DefaultProperty" )) |
118 | defaultProp = obj[QStringLiteral("value" )].toString(); |
119 | if (obj[QStringLiteral("name" )].toString() == QStringLiteral("ParentProperty" )) |
120 | parentProp = obj[QStringLiteral("value" )].toString(); |
121 | } |
122 | |
123 | collectInterfaces(classDef); |
124 | collectSuperClasses(classDef, types, foreign, mode: TopLevel, defaultRevision); |
125 | } |
126 | |
127 | void QmlTypesClassDescription::collect( |
128 | const QJsonObject *classDef, const QVector<QJsonObject> &types, |
129 | const QVector<QJsonObject> &foreign, CollectMode mode, QTypeRevision defaultRevision) |
130 | { |
131 | if (file.isEmpty()) |
132 | file = classDef->value(key: QLatin1String("inputFile" )).toString(); |
133 | |
134 | const auto classInfos = classDef->value(key: QLatin1String("classInfos" )).toArray(); |
135 | const QString classDefName = classDef->value(key: QLatin1String("className" )).toString(); |
136 | const QStringList namespaces = MetaTypesJsonProcessor::namespaces(classDef: *classDef); |
137 | QString foreignTypeName; |
138 | bool explicitCreatable = false; |
139 | for (const QJsonValue classInfo : classInfos) { |
140 | const QJsonObject obj = classInfo.toObject(); |
141 | const QString name = obj[QLatin1String("name" )].toString(); |
142 | const QString value = obj[QLatin1String("value" )].toString(); |
143 | |
144 | if (name == QLatin1String("DefaultProperty" )) { |
145 | if (mode != RelatedType && defaultProp.isEmpty()) |
146 | defaultProp = value; |
147 | } else if (name == QLatin1String("ParentProperty" )) { |
148 | if (mode != RelatedType && parentProp.isEmpty()) |
149 | parentProp = value; |
150 | } else if (name == QLatin1String("QML.AddedInVersion" )) { |
151 | const QTypeRevision revision = QTypeRevision::fromEncodedVersion(value: value.toInt()); |
152 | if (mode == TopLevel) { |
153 | addedInRevision = revision; |
154 | revisions.append(t: revision); |
155 | } else if (!elementName.isEmpty()) { |
156 | revisions.append(t: revision); |
157 | } |
158 | } |
159 | |
160 | if (mode != TopLevel) |
161 | continue; |
162 | |
163 | // These only apply to the original class |
164 | if (name == QLatin1String("QML.Element" )) { |
165 | if (value == QLatin1String("auto" )) |
166 | elementName = classDefName; |
167 | else if (value != QLatin1String("anonymous" )) |
168 | elementName = value; |
169 | } else if (name == QLatin1String("QML.RemovedInVersion" )) { |
170 | removedInRevision = QTypeRevision::fromEncodedVersion(value: value.toInt()); |
171 | } else if (name == QLatin1String("QML.Creatable" )) { |
172 | isCreatable = (value != QLatin1String("false" )); |
173 | explicitCreatable = true; |
174 | } else if (name == QLatin1String("QML.Attached" )) { |
175 | if (const QJsonObject *attached = collectRelated( |
176 | related: value, types, foreign, defaultRevision, namespaces)) { |
177 | attachedType = qualifiedClassName(classDef: *attached); |
178 | } |
179 | } else if (name == QLatin1String("QML.Extended" )) { |
180 | if (const QJsonObject *extension = collectRelated( |
181 | related: value, types, foreign, defaultRevision, namespaces)) { |
182 | extensionType = qualifiedClassName(classDef: *extension); |
183 | } |
184 | } else if (name == QLatin1String("QML.ExtensionIsNamespace" )) { |
185 | if (value == QLatin1String("true" )) |
186 | extensionIsNamespace = true; |
187 | } else if (name == QLatin1String("QML.Sequence" )) { |
188 | if (const QJsonObject *element = collectRelated( |
189 | related: value, types, foreign, defaultRevision, namespaces)) { |
190 | sequenceValueType = qualifiedClassName(classDef: *element); |
191 | } else { |
192 | // TODO: get rid of this once we have JSON data for the builtins. |
193 | sequenceValueType = value; |
194 | } |
195 | } else if (name == QLatin1String("QML.Singleton" )) { |
196 | if (value == QLatin1String("true" )) |
197 | isSingleton = true; |
198 | } else if (name == QLatin1String("QML.Foreign" )) { |
199 | foreignTypeName = value; |
200 | } else if (name == QLatin1String("QML.OmitFromQmlTypes" )) { |
201 | if (value == QLatin1String("true" )) |
202 | omitFromQmlTypes = true; |
203 | } else if (name == QLatin1String("QML.HasCustomParser" )) { |
204 | if (value == QLatin1String("true" )) |
205 | hasCustomParser = true; |
206 | } else if (name == QLatin1String("DeferredPropertyNames" )) { |
207 | deferredNames = value.split(sep: u','); |
208 | } else if (name == QLatin1String("ImmediatePropertyNames" )) { |
209 | immediateNames = value.split(sep: u','); |
210 | } |
211 | } |
212 | |
213 | // If the local type is a namespace the result can only be a namespace, |
214 | // no matter what the foreign type is. |
215 | const bool isNamespace = classDef->value(key: QLatin1String("namespace" )).toBool(); |
216 | |
217 | if (!foreignTypeName.isEmpty()) { |
218 | // We can re-use a type with own QML.* macros as target of QML.Foreign |
219 | if (const QJsonObject *other = findType(types: foreign, foreign: types, name: foreignTypeName, namespaces)) { |
220 | classDef = other; |
221 | |
222 | // Default properties are always local. |
223 | defaultProp.clear(); |
224 | |
225 | // Foreign type can have a default property or an attached types |
226 | const auto classInfos = classDef->value(key: QLatin1String("classInfos" )).toArray(); |
227 | for (const QJsonValue classInfo : classInfos) { |
228 | const QJsonObject obj = classInfo.toObject(); |
229 | const QString foreignName = obj[QLatin1String("name" )].toString(); |
230 | const QString foreignValue = obj[QLatin1String("value" )].toString(); |
231 | if (defaultProp.isEmpty() && foreignName == QLatin1String("DefaultProperty" )) { |
232 | defaultProp = foreignValue; |
233 | } else if (parentProp.isEmpty() && foreignName == QLatin1String("ParentProperty" )) { |
234 | parentProp = foreignValue; |
235 | } else if (foreignName == QLatin1String("QML.Attached" )) { |
236 | if (const QJsonObject *attached = collectRelated( |
237 | related: foreignValue, types, foreign, defaultRevision, namespaces)) { |
238 | attachedType = qualifiedClassName(classDef: *attached); |
239 | } |
240 | } else if (foreignName == QLatin1String("QML.Extended" )) { |
241 | if (const QJsonObject *extension = collectRelated( |
242 | related: foreignValue, types, foreign, defaultRevision, namespaces)) { |
243 | extensionType = qualifiedClassName(classDef: *extension); |
244 | } |
245 | } else if (foreignName == QLatin1String("QML.ExtensionIsNamespace" )) { |
246 | if (foreignValue == QLatin1String("true" )) |
247 | extensionIsNamespace = true; |
248 | } else if (foreignName == QLatin1String("QML.Sequence" )) { |
249 | if (const QJsonObject *element = collectRelated( |
250 | related: foreignValue, types, foreign, defaultRevision, namespaces)) { |
251 | sequenceValueType = qualifiedClassName(classDef: *element); |
252 | } |
253 | } |
254 | } |
255 | } else { |
256 | className = foreignTypeName; |
257 | classDef = nullptr; |
258 | } |
259 | } |
260 | |
261 | if (classDef) { |
262 | if (mode == RelatedType || !elementName.isEmpty()) { |
263 | collectExtraVersions(component: classDef, key: QString::fromLatin1(ba: "properties" ), extraVersions&: revisions); |
264 | collectExtraVersions(component: classDef, key: QString::fromLatin1(ba: "slots" ), extraVersions&: revisions); |
265 | collectExtraVersions(component: classDef, key: QString::fromLatin1(ba: "methods" ), extraVersions&: revisions); |
266 | collectExtraVersions(component: classDef, key: QString::fromLatin1(ba: "signals" ), extraVersions&: revisions); |
267 | } |
268 | |
269 | collectSuperClasses(classDef, types, foreign, mode, defaultRevision); |
270 | } |
271 | |
272 | if (mode != TopLevel) |
273 | return; |
274 | |
275 | if (classDef) |
276 | collectInterfaces(classDef); |
277 | |
278 | if (!addedInRevision.isValid()) { |
279 | revisions.append(t: defaultRevision); |
280 | addedInRevision = defaultRevision; |
281 | } else if (addedInRevision < defaultRevision) { |
282 | revisions.append(t: defaultRevision); |
283 | } |
284 | |
285 | std::sort(first: revisions.begin(), last: revisions.end()); |
286 | const auto end = std::unique(first: revisions.begin(), last: revisions.end()); |
287 | revisions.erase(abegin: QList<QTypeRevision>::const_iterator(end), aend: revisions.constEnd()); |
288 | |
289 | resolvedClass = classDef; |
290 | if (className.isEmpty() && classDef) |
291 | className = qualifiedClassName(classDef: *classDef); |
292 | |
293 | if (!sequenceValueType.isEmpty()) { |
294 | isCreatable = false; |
295 | accessSemantics = QLatin1String("sequence" ); |
296 | } else if (isNamespace) { |
297 | isCreatable = false; |
298 | accessSemantics = QLatin1String("none" ); |
299 | } else if (classDef && classDef->value(key: QLatin1String("object" )).toBool()) { |
300 | accessSemantics = QLatin1String("reference" ); |
301 | } else { |
302 | if (!explicitCreatable) |
303 | isCreatable = false; |
304 | |
305 | if (!classDef) { |
306 | if (elementName.isEmpty() || elementName[0].isLower()) { |
307 | // If no classDef, we generally assume it's a value type defined by the |
308 | // foreign/extended trick. |
309 | accessSemantics = QLatin1String("value" ); |
310 | } else { |
311 | // Objects and namespaces always have metaobjects and therefore classDefs. |
312 | // However, we may not be able to resolve the metaobject at compile time. See |
313 | // the "Invisible" test case. In that case, we must not assume anything about |
314 | // access semantics. |
315 | |
316 | qWarning() << "Warning: Refusing to generate non-lowercase name" |
317 | << elementName << "for unknown foreign type" ; |
318 | elementName.clear(); |
319 | |
320 | // Make it completely inaccessible. |
321 | // We cannot get enums from anonymous types after all. |
322 | accessSemantics = QLatin1String("none" ); |
323 | } |
324 | } else if (classDef->value(key: QLatin1String("gadget" )).toBool()) { |
325 | accessSemantics = QLatin1String("value" ); |
326 | } else { |
327 | accessSemantics = QLatin1String("none" ); |
328 | } |
329 | } |
330 | } |
331 | |
332 | const QJsonObject *QmlTypesClassDescription::collectRelated( |
333 | const QString &related, const QVector<QJsonObject> &types, |
334 | const QVector<QJsonObject> &foreign, QTypeRevision defaultRevision, |
335 | const QStringList &namespaces) |
336 | { |
337 | if (const QJsonObject *other = findType(types, foreign, name: related, namespaces)) { |
338 | collect(classDef: other, types, foreign, mode: RelatedType, defaultRevision); |
339 | return other; |
340 | } |
341 | return nullptr; |
342 | } |
343 | |
344 | |
345 | QT_END_NAMESPACE |
346 | |