1 | // Copyright (C) 2017 Ford Motor Company |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include <qjsonvalue.h> |
5 | #include <qjsonarray.h> |
6 | #include <qjsonobject.h> |
7 | |
8 | #include "utils.h" |
9 | #include "repparser.h" |
10 | |
11 | |
12 | #define _(X) QLatin1String(X) |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | namespace JSON |
17 | { |
18 | enum Types { |
19 | Any, |
20 | Array, |
21 | Object, |
22 | String, |
23 | Bool |
24 | }; |
25 | |
26 | static QJsonValue getItem(const QJsonValue &json, const char *key, JSON::Types type = JSON::Any) |
27 | { |
28 | if (json.isUndefined()) |
29 | qCritical() << "Invalid metadata json file. Unexpected Undefined value when looking for key:" << key; |
30 | if (!json.isObject()) |
31 | qCritical() << "Invalid metadata json file. Input (" << json << ") is not an object when looking for key:" << key; |
32 | QJsonValue value = json.toObject()[_(key)]; |
33 | switch (type) { |
34 | case JSON::Any: break; |
35 | case JSON::Array: |
36 | if (!value.isArray()) |
37 | qCritical() << "Invalid metadata json file. Value (" << value << ") is not an array when looking for key:" << key; |
38 | break; |
39 | case JSON::Object: |
40 | if (!value.isObject()) |
41 | qCritical() << "Invalid metadata json file. Value (" << value << ") is not an object when looking for key:" << key; |
42 | break; |
43 | case JSON::String: |
44 | if (!value.isString()) |
45 | qCritical() << "Invalid metadata json file. Value (" << value << ") is not a string when looking for key:" << key; |
46 | break; |
47 | case JSON::Bool: |
48 | if (!value.isBool()) |
49 | qCritical() << "Invalid metadata json file. Value (" << value << ") is not a bool when looking for key:" << key; |
50 | break; |
51 | } |
52 | return value; |
53 | } |
54 | |
55 | static bool containsKey(const QJsonValue &json, const char *key) |
56 | { |
57 | if (json.isUndefined()) |
58 | qCritical() << "Invalid metadata json file. Unexpected Undefined value when looking for key:" << key; |
59 | if (!json.isObject()) |
60 | qCritical() << "Invalid metadata json file. Input (" << json << ") is not an object when looking for key:" << key; |
61 | return json.toObject().contains(_(key)); |
62 | } |
63 | |
64 | static bool isEmptyArray(const QJsonValue &json, const char *key) |
65 | { |
66 | if (!containsKey(json, key)) |
67 | return true; |
68 | const auto value = getItem(json, key); |
69 | if (!value.isArray()) |
70 | qCritical() << "Invalid metadata json file." << key << "is not an array." ; |
71 | return value.toArray().count() == 0; |
72 | } |
73 | |
74 | static QJsonArray getArray(const QJsonValue &json, const char *key) |
75 | { |
76 | return getItem(json, key, type: JSON::Array).toArray(); |
77 | } |
78 | static QString getString(const QJsonValue &json, const char *key) |
79 | { |
80 | return getItem(json, key, type: JSON::String).toString(); |
81 | } |
82 | static QByteArray getBytes(const QJsonValue &json, const char *key) |
83 | { |
84 | return getItem(json, key, type: JSON::String).toString().toLatin1(); |
85 | } |
86 | static bool getBool(const QJsonValue &json, const char *key) |
87 | { |
88 | return getItem(json, key, type: JSON::Bool).toBool(); |
89 | } |
90 | static bool getBool(const QJsonValue &json, const char *key, bool missingValue) |
91 | { |
92 | if (!containsKey(json, key)) |
93 | return missingValue; |
94 | bool res = getBool(json, key); |
95 | return res; |
96 | } |
97 | } |
98 | |
99 | using namespace JSON; |
100 | |
101 | static QByteArray join(const QByteArrayList &array, const QByteArray &separator) |
102 | { |
103 | QByteArray res; |
104 | const auto sz = array.size(); |
105 | if (!sz) |
106 | return res; |
107 | for (qsizetype i = 0; i < sz - 1; i++) |
108 | res += array.at(i) + separator; |
109 | res += array.at(i: sz - 1); |
110 | return res; |
111 | } |
112 | |
113 | static QByteArrayList generateProperties(const QJsonArray &properties, bool isPod=false) |
114 | { |
115 | QByteArrayList ret; |
116 | for (const QJsonValue prop : properties) { |
117 | if (!isPod && !containsKey(json: prop, key: "notify" ) && !getBool(json: prop, key: "constant" )) { |
118 | qWarning() << "Skipping property" << getString(json: prop, key: "name" ) |
119 | << "because it is non-notifiable & non-constant" ; |
120 | continue; // skip non-notifiable properties |
121 | } |
122 | QByteArray output = getBytes(json: prop, key: "type" ) + " " + getBytes(json: prop, key: "name" ); |
123 | if (getBool(json: prop, key: "constant" )) |
124 | output += " CONSTANT" ; |
125 | if (!containsKey(json: prop, key: "write" ) && containsKey(json: prop, key: "read." )) |
126 | output += " READONLY" ; |
127 | ret << output; |
128 | } |
129 | return ret; |
130 | } |
131 | |
132 | static QByteArray generateFunctions(const QByteArray &type, const QJsonArray &functions) |
133 | { |
134 | QByteArray ret; |
135 | for (const QJsonValue func : functions) { |
136 | ret += type + "(" + getBytes(json: func, key: "returnType" ) + " " + getBytes(json: func, key: "name" ) + "(" ; |
137 | const auto arguments = getArray(json: func, key: "arguments" ); |
138 | for (const QJsonValue arg : arguments) |
139 | ret += getBytes(json: arg, key: "type" ) + " " + getBytes(json: arg, key: "name" ) + ", " ; |
140 | if (arguments.count()) |
141 | ret.chop(n: 2); |
142 | ret += "));\n" ; |
143 | } |
144 | return ret; |
145 | } |
146 | |
147 | const auto filterNotPublic = [](const QJsonValue &value) { |
148 | return getString(json: value, key: "access" ) != QStringLiteral("public" ); |
149 | }; |
150 | |
151 | static QJsonArray cleanedSignalList(const QJsonValue &cls) |
152 | { |
153 | if (isEmptyArray(json: cls, key: "signals" )) |
154 | return QJsonArray(); |
155 | |
156 | auto signalList = getArray(json: cls, key: "signals" ); |
157 | if (isEmptyArray(json: cls, key: "properties" )) |
158 | return signalList; |
159 | |
160 | const auto props = getArray(json: cls, key: "properties" ); |
161 | const auto filterNotify = [&props](const QJsonValue &value) { |
162 | const auto filter = [&value](const QJsonValue &prop) { |
163 | return getItem(json: value, key: "name" ) == getItem(json: prop, key: "notify" ); |
164 | }; |
165 | return std::find_if(first: props.begin(), last: props.end(), pred: filter) != props.end(); |
166 | }; |
167 | for (auto it = signalList.begin(); it != signalList.end(); /* blank */ ) { |
168 | if (filterNotify(*it)) |
169 | it = signalList.erase(it); |
170 | else if (filterNotPublic(*it)) |
171 | it = signalList.erase(it); |
172 | else |
173 | it++; |
174 | } |
175 | return signalList; |
176 | } |
177 | |
178 | static QJsonArray cleanedSlotList(const QJsonValue &cls) |
179 | { |
180 | if (isEmptyArray(json: cls, key: "slots" )) |
181 | return QJsonArray(); |
182 | |
183 | auto slotList = getArray(json: cls, key: "slots" ); |
184 | if (!isEmptyArray(json: cls, key: "properties" )) |
185 | return slotList; |
186 | |
187 | const auto props = getArray(json: cls, key: "properties" ); |
188 | const auto filterWrite = [&props](const QJsonValue &value) { |
189 | const auto filter = [&value](const QJsonValue &prop) { |
190 | const auto args = getArray(json: prop, key: "arguments" ); |
191 | return getItem(json: value, key: "name" ) == getItem(json: prop, key: "write" ) && args.count() == 1 |
192 | && getItem(json: args.at(i: 0), key: "type" ) == getItem(json: prop, key: "type" ); |
193 | }; |
194 | return std::find_if(first: props.begin(), last: props.end(), pred: filter) != props.end(); |
195 | }; |
196 | for (auto it = slotList.begin(); it != slotList.end(); /* blank */ ) { |
197 | if (filterWrite(*it)) |
198 | it = slotList.erase(it); |
199 | else if (filterNotPublic(*it)) |
200 | it = slotList.erase(it); |
201 | else |
202 | it++; |
203 | } |
204 | return slotList; |
205 | } |
206 | |
207 | QByteArray generateClass(const QJsonValue &cls, bool alwaysGenerateClass) |
208 | { |
209 | if (getBool(json: cls, key: "gadget" , missingValue: false) || alwaysGenerateClass |
210 | || (isEmptyArray(json: cls, key: "signals" ) && isEmptyArray(json: cls, key: "slots" ))) |
211 | return "POD " + getBytes(json: cls, key: "className" ) + "(" |
212 | + join(array: generateProperties(properties: getArray(json: cls, key: "properties" ), isPod: true), separator: ", " ) + ")\n" ; |
213 | |
214 | QByteArray ret("class " + getBytes(json: cls, key: "className" ) + "\n{\n" ); |
215 | if (!isEmptyArray(json: cls, key: "properties" )) |
216 | ret += " PROP(" + join(array: generateProperties(properties: getArray(json: cls, key: "properties" )), separator: ");\n PROP(" ) |
217 | + ");\n" ; |
218 | ret += generateFunctions(type: " SLOT" , functions: cleanedSlotList(cls)); |
219 | ret += generateFunctions(type: " SIGNAL" , functions: cleanedSignalList(cls)); |
220 | ret += "}\n" ; |
221 | return ret; |
222 | } |
223 | |
224 | static QList<PODAttribute> propertyList2PODAttributes(const QJsonArray &list) |
225 | { |
226 | QList<PODAttribute> ret; |
227 | for (const QJsonValue prop : list) |
228 | ret.push_back(PODAttribute(getString(json: prop, key: "type" ), getString(json: prop, key: "name" ))); |
229 | return ret; |
230 | } |
231 | |
232 | QList<ASTProperty> propertyList2AstProperties(const QJsonArray &list) |
233 | { |
234 | QList<ASTProperty> ret; |
235 | for (const QJsonValue property : list) { |
236 | if (!containsKey(json: property, key: "notify" ) && !getBool(json: property, key: "constant" )) { |
237 | qWarning() << "Skipping property" << getString(json: property, key: "name" ) |
238 | << "because it is non-notifiable & non-constant" ; |
239 | continue; // skip non-notifiable properties |
240 | } |
241 | ASTProperty prop; |
242 | prop.name = getString(json: property, key: "name" ); |
243 | prop.type = getString(json: property, key: "type" ); |
244 | prop.modifier = getBool(property, "constant" ) ? ASTProperty::Constant |
245 | : !containsKey(property, "write" ) && containsKey(property, "read" ) |
246 | ? ASTProperty::ReadOnly |
247 | : ASTProperty::ReadWrite; |
248 | ret.push_back(prop); |
249 | } |
250 | return ret; |
251 | } |
252 | |
253 | QList<ASTFunction> functionList2AstFunctionList(const QJsonArray &list) |
254 | { |
255 | QList<ASTFunction> ret; |
256 | for (const QJsonValue function : list) { |
257 | ASTFunction func; |
258 | func.name = getString(json: function, key: "name" ); |
259 | func.returnType = getString(json: function, key: "returnType" ); |
260 | const auto arguments = getArray(json: function, key: "arguments" ); |
261 | for (const QJsonValue arg : arguments) |
262 | func.params.push_back(ASTDeclaration(getString(json: arg, key: "type" ), getString(json: arg, key: "name" ))); |
263 | ret.push_back(func); |
264 | } |
265 | return ret; |
266 | } |
267 | |
268 | AST classList2AST(const QJsonArray &classes) |
269 | { |
270 | AST ret; |
271 | for (const QJsonValue cls : classes) { |
272 | if (isEmptyArray(json: cls, key: "signals" ) && isEmptyArray(json: cls, key: "slots" )) { |
273 | POD pod; |
274 | pod.name = getString(json: cls, key: "className" ); |
275 | pod.attributes = propertyList2PODAttributes(getArray(cls, "properties" )); |
276 | ret.pods.push_back(pod); |
277 | } else { |
278 | ASTClass cl(getString(cls, "className" )); |
279 | cl.properties = propertyList2AstProperties(getArray(cls, "properties" )); |
280 | cl.signalsList = functionList2AstFunctionList(cleanedSignalList(cls)); |
281 | cl.slotsList = functionList2AstFunctionList(cleanedSlotList(cls)); |
282 | ret.classes.push_back(cl); |
283 | } |
284 | } |
285 | return ret; |
286 | } |
287 | |
288 | QT_END_NAMESPACE |
289 | |