1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qscxmlglobals_p.h" |
5 | #include "qscxmlinvokableservice_p.h" |
6 | #include "qscxmlstatemachine_p.h" |
7 | |
8 | QT_BEGIN_NAMESPACE |
9 | |
10 | /*! |
11 | * \class QScxmlInvokableService |
12 | * \brief The QScxmlInvokableService class is the base class for services called |
13 | * from state machines. |
14 | * \since 5.8 |
15 | * \inmodule QtScxml |
16 | * |
17 | * The services are called from state machines via the mechanism described in |
18 | * \l {SCXML Specification - 6.4 <invoke>}. This class represents an actual |
19 | * instance of an invoked service. |
20 | */ |
21 | |
22 | /*! |
23 | * \class QScxmlInvokableServiceFactory |
24 | * \brief The QScxmlInvokableServiceFactory class creates invokable service |
25 | * instances. |
26 | * \since 5.8 |
27 | * \inmodule QtScxml |
28 | * |
29 | * Each service instance represents |
30 | * an \c <invoke> element in the SCXML document. Each time the service is |
31 | * actually invoked, a new instance of QScxmlInvokableService is created. |
32 | */ |
33 | |
34 | /*! |
35 | \property QScxmlInvokableServiceFactory::invokeInfo |
36 | |
37 | \brief The QScxmlExecutableContent::InvokeInfo passed to the constructor. |
38 | */ |
39 | |
40 | /*! |
41 | \property QScxmlInvokableServiceFactory::names |
42 | |
43 | \brief The names passed to the constructor. |
44 | */ |
45 | |
46 | /*! |
47 | \property QScxmlInvokableServiceFactory::parameters |
48 | |
49 | \brief The parameters passed to the constructor. |
50 | */ |
51 | |
52 | /*! |
53 | * \class QScxmlStaticScxmlServiceFactory |
54 | * \brief The QScxmlStaticScxmlServiceFactory class creates SCXML service |
55 | * instances from precompiled documents. |
56 | * \since 5.8 |
57 | * \inmodule QtScxml |
58 | * |
59 | * A factory for instantiating SCXML state machines from files known at compile |
60 | * time, that is, files specified via the \c src attribute in \c <invoke>. |
61 | */ |
62 | |
63 | /*! |
64 | * \class QScxmlDynamicScxmlServiceFactory |
65 | * \brief The QScxmlDynamicScxmlServiceFactory class creates SCXML service |
66 | * instances from documents loaded at runtime. |
67 | * \since 5.8 |
68 | * \inmodule QtScxml |
69 | * |
70 | * Dynamically resolved services are used when loading \l{SCXML Specification} |
71 | * {SCXML} content from files that a |
72 | * parent state machine requests at runtime, via the \c srcexpr attribute in |
73 | * the \c <invoke> element. |
74 | */ |
75 | |
76 | /*! |
77 | * \property QScxmlInvokableService::parentStateMachine |
78 | * |
79 | * \brief The SCXML state machine that invoked the service. |
80 | */ |
81 | |
82 | /*! |
83 | * \property QScxmlInvokableService::id |
84 | * |
85 | * \brief The ID of the invokable service. |
86 | * |
87 | * The ID is specified by the \c id attribute of the \c <invoke> element. |
88 | */ |
89 | |
90 | /*! |
91 | * \property QScxmlInvokableService::name |
92 | * |
93 | * \brief The name of the service being invoked. |
94 | */ |
95 | |
96 | /*! |
97 | * \fn QScxmlInvokableService::postEvent(QScxmlEvent *event) |
98 | * |
99 | * Sends an \a event to the service. |
100 | */ |
101 | |
102 | /*! |
103 | * \fn QScxmlInvokableService::start() |
104 | * |
105 | * Starts the invokable service. Returns \c true on success, or \c false if the |
106 | * invocation fails. |
107 | */ |
108 | |
109 | /*! |
110 | * \fn QScxmlInvokableServiceFactory::invoke(QScxmlStateMachine *parentStateMachine) |
111 | * |
112 | * Invokes the service with the parameters given in the constructor, passing |
113 | * \a parentStateMachine as the parent. Returns the new invokable service. |
114 | */ |
115 | |
116 | QScxmlInvokableServicePrivate::QScxmlInvokableServicePrivate(QScxmlStateMachine *parentStateMachine) |
117 | : parentStateMachine(parentStateMachine) |
118 | { |
119 | static int metaType = qRegisterMetaType<QScxmlInvokableService *>(); |
120 | Q_UNUSED(metaType); |
121 | } |
122 | |
123 | QScxmlInvokableServiceFactoryPrivate::QScxmlInvokableServiceFactoryPrivate( |
124 | const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
125 | const QList<QScxmlExecutableContent::StringId> &namelist, |
126 | const QList<QScxmlExecutableContent::ParameterInfo> ¶meters) |
127 | : invokeInfo(invokeInfo) |
128 | , names(namelist) |
129 | , parameters(parameters) |
130 | {} |
131 | |
132 | QScxmlStaticScxmlServiceFactoryPrivate::QScxmlStaticScxmlServiceFactoryPrivate( |
133 | const QMetaObject *metaObject, |
134 | const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
135 | const QList<QScxmlExecutableContent::StringId> &names, |
136 | const QList<QScxmlExecutableContent::ParameterInfo> ¶meters) |
137 | : QScxmlInvokableServiceFactoryPrivate(invokeInfo, names, parameters), metaObject(metaObject) |
138 | { |
139 | } |
140 | |
141 | QScxmlInvokableService::QScxmlInvokableService(QScxmlStateMachine *parentStateMachine, |
142 | QScxmlInvokableServiceFactory *factory) : |
143 | QObject(*(new QScxmlInvokableServicePrivate(parentStateMachine)), factory) |
144 | { |
145 | } |
146 | |
147 | QScxmlStateMachine *QScxmlInvokableService::parentStateMachine() const |
148 | { |
149 | Q_D(const QScxmlInvokableService); |
150 | return d->parentStateMachine; |
151 | } |
152 | |
153 | QScxmlInvokableServiceFactory::QScxmlInvokableServiceFactory( |
154 | const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
155 | const QList<QScxmlExecutableContent::StringId> &names, |
156 | const QList<QScxmlExecutableContent::ParameterInfo> ¶meters, |
157 | QObject *parent) |
158 | : QObject(*(new QScxmlInvokableServiceFactoryPrivate(invokeInfo, names, parameters)), parent) |
159 | {} |
160 | |
161 | const QScxmlExecutableContent::InvokeInfo &QScxmlInvokableServiceFactory::invokeInfo() const |
162 | { |
163 | Q_D(const QScxmlInvokableServiceFactory); |
164 | return d->invokeInfo; |
165 | } |
166 | |
167 | const QList<QScxmlExecutableContent::ParameterInfo> & |
168 | QScxmlInvokableServiceFactory::parameters() const |
169 | { |
170 | Q_D(const QScxmlInvokableServiceFactory); |
171 | return d->parameters; |
172 | } |
173 | |
174 | const QList<QScxmlExecutableContent::StringId> &QScxmlInvokableServiceFactory::names() const |
175 | { |
176 | Q_D(const QScxmlInvokableServiceFactory); |
177 | return d->names; |
178 | } |
179 | |
180 | QString calculateSrcexpr(QScxmlStateMachine *parent, QScxmlExecutableContent::EvaluatorId srcexpr, |
181 | bool *ok) |
182 | { |
183 | Q_ASSERT(ok); |
184 | *ok = true; |
185 | auto dataModel = parent->dataModel(); |
186 | |
187 | if (srcexpr != QScxmlExecutableContent::NoEvaluator) { |
188 | *ok = false; |
189 | auto v = dataModel->evaluateToString(id: srcexpr, ok); |
190 | if (!*ok) |
191 | return QString(); |
192 | return v; |
193 | } |
194 | |
195 | return QString(); |
196 | } |
197 | |
198 | QString QScxmlInvokableServicePrivate::calculateId( |
199 | QScxmlStateMachine *parent, const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
200 | bool *ok) const |
201 | { |
202 | Q_ASSERT(ok); |
203 | *ok = true; |
204 | auto stateMachine = parent->tableData(); |
205 | |
206 | if (invokeInfo.id != QScxmlExecutableContent::NoString) { |
207 | return stateMachine->string(id: invokeInfo.id); |
208 | } |
209 | |
210 | const QString newId = QScxmlStateMachinePrivate::generateSessionId( |
211 | prefix: stateMachine->string(id: invokeInfo.prefix)); |
212 | |
213 | if (invokeInfo.location != QScxmlExecutableContent::NoString) { |
214 | auto idloc = stateMachine->string(id: invokeInfo.location); |
215 | auto ctxt = stateMachine->string(id: invokeInfo.context); |
216 | *ok = parent->dataModel()->setScxmlProperty(name: idloc, value: newId, context: ctxt); |
217 | if (!*ok) |
218 | return QString(); |
219 | } |
220 | |
221 | return newId; |
222 | } |
223 | |
224 | QVariantMap QScxmlInvokableServicePrivate::calculateData( |
225 | QScxmlStateMachine *parent, |
226 | const QList<QScxmlExecutableContent::ParameterInfo> ¶meters, |
227 | const QList<QScxmlExecutableContent::StringId> &names, |
228 | bool *ok) const |
229 | { |
230 | Q_ASSERT(ok); |
231 | |
232 | QVariantMap result; |
233 | auto dataModel = parent->dataModel(); |
234 | auto tableData = parent->tableData(); |
235 | |
236 | for (const QScxmlExecutableContent::ParameterInfo ¶m : parameters) { |
237 | auto name = tableData->string(id: param.name); |
238 | |
239 | if (param.expr != QScxmlExecutableContent::NoEvaluator) { |
240 | *ok = false; |
241 | auto v = dataModel->evaluateToVariant(id: param.expr, ok); |
242 | if (!*ok) |
243 | return QVariantMap(); |
244 | result.insert(key: name, value: v); |
245 | } else { |
246 | QString loc; |
247 | if (param.location != QScxmlExecutableContent::NoString) { |
248 | loc = tableData->string(id: param.location); |
249 | } |
250 | |
251 | if (loc.isEmpty()) { |
252 | // TODO: error message? |
253 | *ok = false; |
254 | return QVariantMap(); |
255 | } |
256 | |
257 | auto v = dataModel->scxmlProperty(name: loc); |
258 | result.insert(key: name, value: v); |
259 | } |
260 | } |
261 | |
262 | for (QScxmlExecutableContent::StringId locid : names) { |
263 | QString loc; |
264 | if (locid != QScxmlExecutableContent::NoString) { |
265 | loc = tableData->string(id: locid); |
266 | } |
267 | if (loc.isEmpty()) { |
268 | // TODO: error message? |
269 | *ok = false; |
270 | return QVariantMap(); |
271 | } |
272 | if (dataModel->hasScxmlProperty(name: loc)) { |
273 | auto v = dataModel->scxmlProperty(name: loc); |
274 | result.insert(key: loc, value: v); |
275 | } else { |
276 | *ok = false; |
277 | return QVariantMap(); |
278 | } |
279 | } |
280 | |
281 | return result; |
282 | } |
283 | |
284 | QScxmlScxmlService::~QScxmlScxmlService() |
285 | { |
286 | delete m_stateMachine; |
287 | } |
288 | |
289 | /*! |
290 | Creates a SCXML service wrapping \a stateMachine, invoked from |
291 | \a parentStateMachine, as a child of \a factory. |
292 | */ |
293 | QScxmlScxmlService::QScxmlScxmlService(QScxmlStateMachine *stateMachine, |
294 | QScxmlStateMachine *parentStateMachine, |
295 | QScxmlInvokableServiceFactory *factory) |
296 | : QScxmlInvokableService(parentStateMachine, factory), m_stateMachine(stateMachine) |
297 | { |
298 | QScxmlStateMachinePrivate::get(t: stateMachine)->m_parentStateMachine = parentStateMachine; |
299 | } |
300 | |
301 | /*! |
302 | * \reimp |
303 | */ |
304 | bool QScxmlScxmlService::start() |
305 | { |
306 | Q_D(QScxmlInvokableService); |
307 | qCDebug(qscxmlLog) << parentStateMachine() << "preparing to start" << m_stateMachine; |
308 | |
309 | const QScxmlInvokableServiceFactory *factory |
310 | = qobject_cast<QScxmlInvokableServiceFactory *>(object: parent()); |
311 | Q_ASSERT(factory); |
312 | |
313 | bool ok = false; |
314 | auto id = d->calculateId(parent: parentStateMachine(), invokeInfo: factory->invokeInfo(), ok: &ok); |
315 | if (!ok) |
316 | return false; |
317 | auto data = d->calculateData(parent: parentStateMachine(), parameters: factory->parameters(), names: factory->names(), |
318 | ok: &ok); |
319 | if (!ok) |
320 | return false; |
321 | |
322 | QScxmlStateMachinePrivate::get(t: m_stateMachine)->m_sessionId = id; |
323 | m_stateMachine->setInitialValues(data); |
324 | if (m_stateMachine->init()) { |
325 | qCDebug(qscxmlLog) << parentStateMachine() << "starting" << m_stateMachine; |
326 | m_stateMachine->start(); |
327 | return true; |
328 | } |
329 | |
330 | qCDebug(qscxmlLog) << parentStateMachine() << "failed to start" << m_stateMachine; |
331 | return false; |
332 | } |
333 | |
334 | /*! |
335 | \reimp |
336 | */ |
337 | QString QScxmlScxmlService::id() const |
338 | { |
339 | return m_stateMachine->sessionId(); |
340 | } |
341 | |
342 | /*! |
343 | \reimp |
344 | */ |
345 | QString QScxmlScxmlService::name() const |
346 | { |
347 | return m_stateMachine->name(); |
348 | } |
349 | |
350 | /*! |
351 | \reimp |
352 | */ |
353 | void QScxmlScxmlService::postEvent(QScxmlEvent *event) |
354 | { |
355 | QScxmlStateMachinePrivate::get(t: m_stateMachine)->postEvent(event); |
356 | } |
357 | |
358 | QScxmlStateMachine *QScxmlScxmlService::stateMachine() const |
359 | { |
360 | return m_stateMachine; |
361 | } |
362 | |
363 | /*! |
364 | Creates a factory for dynamically resolved services, passing the attributes of |
365 | the \c <invoke> element as \a invokeInfo, any \c <param> child elements as |
366 | \a parameters, the content of the \c names attribute as \a names, and the |
367 | QObject parent \a parent. |
368 | */ |
369 | QScxmlDynamicScxmlServiceFactory::QScxmlDynamicScxmlServiceFactory( |
370 | const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
371 | const QList<QScxmlExecutableContent::StringId> &names, |
372 | const QList<QScxmlExecutableContent::ParameterInfo> ¶meters, |
373 | QObject *parent) |
374 | : QScxmlInvokableServiceFactory(invokeInfo, names, parameters, parent) |
375 | {} |
376 | |
377 | /*! |
378 | \reimp |
379 | */ |
380 | QScxmlInvokableService *QScxmlDynamicScxmlServiceFactory::invoke( |
381 | QScxmlStateMachine *parentStateMachine) |
382 | { |
383 | bool ok = true; |
384 | auto srcexpr = calculateSrcexpr(parent: parentStateMachine, srcexpr: invokeInfo().expr, ok: &ok); |
385 | if (!ok) |
386 | return nullptr; |
387 | |
388 | return invokeDynamicScxmlService(sourceUrl: srcexpr, parentStateMachine, factory: this); |
389 | } |
390 | |
391 | QScxmlStaticScxmlServiceFactory::QScxmlStaticScxmlServiceFactory( |
392 | const QMetaObject *metaObject, |
393 | const QScxmlExecutableContent::InvokeInfo &invokeInfo, |
394 | const QList<QScxmlExecutableContent::StringId> &nameList, |
395 | const QList<QScxmlExecutableContent::ParameterInfo> ¶meters, |
396 | QObject *parent) |
397 | : QScxmlInvokableServiceFactory(*(new QScxmlStaticScxmlServiceFactoryPrivate( |
398 | metaObject, invokeInfo, nameList, parameters)), parent) |
399 | { |
400 | } |
401 | |
402 | /*! |
403 | \reimp |
404 | */ |
405 | QScxmlInvokableService *QScxmlStaticScxmlServiceFactory::invoke( |
406 | QScxmlStateMachine *parentStateMachine) |
407 | { |
408 | Q_D(const QScxmlStaticScxmlServiceFactory); |
409 | QScxmlStateMachine *instance = qobject_cast<QScxmlStateMachine *>( |
410 | object: d->metaObject->newInstance(Q_ARG(QObject *, this))); |
411 | return instance ? invokeStaticScxmlService(childStateMachine: instance, parentStateMachine, factory: this) : nullptr; |
412 | } |
413 | |
414 | QScxmlInvokableServiceFactory::QScxmlInvokableServiceFactory( |
415 | QScxmlInvokableServiceFactoryPrivate &dd, QObject *parent) |
416 | : QObject(dd, parent) |
417 | {} |
418 | |
419 | QScxmlScxmlService *invokeStaticScxmlService(QScxmlStateMachine *childStateMachine, |
420 | QScxmlStateMachine *parentStateMachine, |
421 | QScxmlInvokableServiceFactory *factory) |
422 | { |
423 | QScxmlStateMachinePrivate::get(t: childStateMachine)->setIsInvoked(true); |
424 | return new QScxmlScxmlService(childStateMachine, parentStateMachine, factory); |
425 | } |
426 | |
427 | QT_END_NAMESPACE |
428 | |