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
8QT_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
116QScxmlInvokableServicePrivate::QScxmlInvokableServicePrivate(QScxmlStateMachine *parentStateMachine)
117 : parentStateMachine(parentStateMachine)
118{
119 static int metaType = qRegisterMetaType<QScxmlInvokableService *>();
120 Q_UNUSED(metaType);
121}
122
123QScxmlInvokableServiceFactoryPrivate::QScxmlInvokableServiceFactoryPrivate(
124 const QScxmlExecutableContent::InvokeInfo &invokeInfo,
125 const QList<QScxmlExecutableContent::StringId> &namelist,
126 const QList<QScxmlExecutableContent::ParameterInfo> &parameters)
127 : invokeInfo(invokeInfo)
128 , names(namelist)
129 , parameters(parameters)
130{}
131
132QScxmlStaticScxmlServiceFactoryPrivate::QScxmlStaticScxmlServiceFactoryPrivate(
133 const QMetaObject *metaObject,
134 const QScxmlExecutableContent::InvokeInfo &invokeInfo,
135 const QList<QScxmlExecutableContent::StringId> &names,
136 const QList<QScxmlExecutableContent::ParameterInfo> &parameters)
137 : QScxmlInvokableServiceFactoryPrivate(invokeInfo, names, parameters), metaObject(metaObject)
138{
139}
140
141QScxmlInvokableService::QScxmlInvokableService(QScxmlStateMachine *parentStateMachine,
142 QScxmlInvokableServiceFactory *factory) :
143 QObject(*(new QScxmlInvokableServicePrivate(parentStateMachine)), factory)
144{
145}
146
147QScxmlStateMachine *QScxmlInvokableService::parentStateMachine() const
148{
149 Q_D(const QScxmlInvokableService);
150 return d->parentStateMachine;
151}
152
153QScxmlInvokableServiceFactory::QScxmlInvokableServiceFactory(
154 const QScxmlExecutableContent::InvokeInfo &invokeInfo,
155 const QList<QScxmlExecutableContent::StringId> &names,
156 const QList<QScxmlExecutableContent::ParameterInfo> &parameters,
157 QObject *parent)
158 : QObject(*(new QScxmlInvokableServiceFactoryPrivate(invokeInfo, names, parameters)), parent)
159{}
160
161const QScxmlExecutableContent::InvokeInfo &QScxmlInvokableServiceFactory::invokeInfo() const
162{
163 Q_D(const QScxmlInvokableServiceFactory);
164 return d->invokeInfo;
165}
166
167const QList<QScxmlExecutableContent::ParameterInfo> &
168QScxmlInvokableServiceFactory::parameters() const
169{
170 Q_D(const QScxmlInvokableServiceFactory);
171 return d->parameters;
172}
173
174const QList<QScxmlExecutableContent::StringId> &QScxmlInvokableServiceFactory::names() const
175{
176 Q_D(const QScxmlInvokableServiceFactory);
177 return d->names;
178}
179
180QString 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
198QString 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
224QVariantMap QScxmlInvokableServicePrivate::calculateData(
225 QScxmlStateMachine *parent,
226 const QList<QScxmlExecutableContent::ParameterInfo> &parameters,
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 &param : 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
284QScxmlScxmlService::~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 */
293QScxmlScxmlService::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 */
304bool 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 */
337QString QScxmlScxmlService::id() const
338{
339 return m_stateMachine->sessionId();
340}
341
342/*!
343 \reimp
344 */
345QString QScxmlScxmlService::name() const
346{
347 return m_stateMachine->name();
348}
349
350/*!
351 \reimp
352 */
353void QScxmlScxmlService::postEvent(QScxmlEvent *event)
354{
355 QScxmlStateMachinePrivate::get(t: m_stateMachine)->postEvent(event);
356}
357
358QScxmlStateMachine *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 */
369QScxmlDynamicScxmlServiceFactory::QScxmlDynamicScxmlServiceFactory(
370 const QScxmlExecutableContent::InvokeInfo &invokeInfo,
371 const QList<QScxmlExecutableContent::StringId> &names,
372 const QList<QScxmlExecutableContent::ParameterInfo> &parameters,
373 QObject *parent)
374 : QScxmlInvokableServiceFactory(invokeInfo, names, parameters, parent)
375{}
376
377/*!
378 \reimp
379 */
380QScxmlInvokableService *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
391QScxmlStaticScxmlServiceFactory::QScxmlStaticScxmlServiceFactory(
392 const QMetaObject *metaObject,
393 const QScxmlExecutableContent::InvokeInfo &invokeInfo,
394 const QList<QScxmlExecutableContent::StringId> &nameList,
395 const QList<QScxmlExecutableContent::ParameterInfo> &parameters,
396 QObject *parent)
397 : QScxmlInvokableServiceFactory(*(new QScxmlStaticScxmlServiceFactoryPrivate(
398 metaObject, invokeInfo, nameList, parameters)), parent)
399{
400}
401
402/*!
403 \reimp
404 */
405QScxmlInvokableService *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
414QScxmlInvokableServiceFactory::QScxmlInvokableServiceFactory(
415 QScxmlInvokableServiceFactoryPrivate &dd, QObject *parent)
416 : QObject(dd, parent)
417{}
418
419QScxmlScxmlService *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
427QT_END_NAMESPACE
428

source code of qtscxml/src/scxml/qscxmlinvokableservice.cpp