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